Skip to content

Instantly share code, notes, and snippets.

@StefMa
Created February 6, 2026 14:12
Show Gist options
  • Select an option

  • Save StefMa/5e0f7c4ae6043d22c803e464f437a915 to your computer and use it in GitHub Desktop.

Select an option

Save StefMa/5e0f7c4ae6043d22c803e464f437a915 to your computer and use it in GitHub Desktop.
aiir – summarize github actions failures with ollama
package main
import (
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
)
type PRCheck struct {
Link string `json:"link"`
Name string `json:"name"`
State string `json:"state"`
Workflow string `json:"workflow"`
}
func main() {
if len(os.Args) != 2 {
_, _ = fmt.Fprintln(os.Stderr, "Usage: ./aiir NUMBER (where NUMBER is a PR number)")
os.Exit(1)
}
prNumber, err := strconv.Atoi(os.Args[1])
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Invalid PR number: %v\n", os.Args[1])
os.Exit(1)
}
if !isGhAvailable() {
_, _ = fmt.Fprintln(os.Stderr, "Error: 'gh' CLI not found in PATH. Please install GitHub CLI.")
os.Exit(1)
}
checks, err := getPRChecks(prNumber)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Error running 'gh pr checks': %v\n", err)
os.Exit(1)
}
failures := []PRCheck{}
for _, check := range checks {
if strings.ToUpper(check.State) == "FAILURE" {
failures = append(failures, check)
}
}
if len(failures) == 0 {
fmt.Printf("All checks succeeded for PR #%d.\n", prNumber)
os.Exit(0)
}
for _, fail := range failures {
jobID, err := extractJobID(fail.Link)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Could not extract job ID from link: %s\n", fail.Link)
continue
}
fmt.Printf("\n--- Failure: %s (Workflow: %s) ---\n", fail.Name, fail.Workflow)
log, err := getJobLog(jobID)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Error getting log for job %s: %v\n", jobID, err)
continue
}
fmt.Println(log)
ollamaOutput, err := runOllama(log)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Error running ollama: %v\n", err)
continue
}
fmt.Printf("\n--- AI Analysis ---\n%s\n", ollamaOutput)
}
// No os.Exit(1) here; always exit 0
}
func isGhAvailable() bool {
_, err := exec.LookPath("gh")
return err == nil
}
func getPRChecks(prNumber int) ([]PRCheck, error) {
cmd := exec.Command("gh", "pr", "checks", strconv.Itoa(prNumber), "--json", "workflow,name,link,state")
output, err := cmd.Output()
if err != nil {
return nil, err
}
var checks []PRCheck
if err := json.Unmarshal(output, &checks); err != nil {
return nil, err
}
return checks, nil
}
func extractJobID(link string) (string, error) {
parts := strings.Split(strings.TrimRight(link, "/"), "/")
if len(parts) < 1 {
return "", errors.New("link does not contain enough segments")
}
jobID := parts[len(parts)-1]
if _, err := strconv.ParseInt(jobID, 10, 64); err != nil {
return "", errors.New("last segment is not a valid job ID")
}
return jobID, nil
}
func getJobLog(jobID string) (string, error) {
cmd := exec.Command("gh", "run", "view", "--job", jobID, "--log-failed")
output, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("%v: %s", err, string(output))
}
return string(output), nil
}
// runOllama calls the ollama CLI with the log and a prompt, returns the AI output.
func runOllama(log string) (string, error) {
instructions := `
Task: Analyze the CI job log above.
Rules:
• Use only information explicitly present in the log.
• Do not infer, guess, or add context beyond the log.
• Ignore all warnings, notices, and informational messages.
• Focus only on errors that cause or contribute to job failure.
Output:
• Identify the primary error(s).
• Point to the exact location if available (file name, path, line number, module, step, or command).
• Briefly explain why the error occurred, strictly based on the log.
If the log does not contain enough information to locate the issue in the code, explicitly say so.
`
prompt := log + "\n\n/nothink\n\n" + instructions
cmd := exec.Command("ollama", "run", "qwen3:8b")
stdin, err := cmd.StdinPipe()
if err != nil {
return "", err
}
go func() {
// Ignore error from Close for linter cleanliness
defer func() { _ = stdin.Close() }()
_, _ = stdin.Write([]byte(prompt))
}()
output, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("%v: %s", err, string(output))
}
return string(output), nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment