Skip to content

Instantly share code, notes, and snippets.

@trashhalo
Created February 23, 2026 16:03
Show Gist options
  • Select an option

  • Save trashhalo/f85a0067ef02d5012a38229270c7dbb6 to your computer and use it in GitHub Desktop.

Select an option

Save trashhalo/f85a0067ef02d5012a38229270c7dbb6 to your computer and use it in GitHub Desktop.
Claude Code build failures skill
#!/usr/bin/env -S uv run
# /// script
# dependencies = []
# ///
"""
Find and report build failures from the latest GitHub Actions run.
This script finds the most recent CI run for the current branch and extracts
error messages with context from any failed jobs.
"""
import json
import subprocess
import sys
import re
from typing import Any, Dict, List, Optional
def run_gh_command(args: List[str]) -> str:
"""Run gh CLI command and return output."""
try:
result = subprocess.run(
["gh"] + args,
capture_output=True,
text=True,
check=True,
)
return result.stdout
except subprocess.CalledProcessError as e:
print(f"Error running gh command: {e.stderr}", file=sys.stderr)
sys.exit(1)
except FileNotFoundError:
print("Error: gh CLI not found. Please install it.", file=sys.stderr)
sys.exit(1)
def get_current_branch() -> str:
"""Get the current git branch name."""
try:
result = subprocess.run(
["git", "branch", "--show-current"],
capture_output=True,
text=True,
check=True,
)
return result.stdout.strip()
except subprocess.CalledProcessError:
print("Error: Could not determine current branch", file=sys.stderr)
sys.exit(1)
def get_latest_ci_run(branch: str) -> Optional[Dict[str, Any]]:
"""Get the latest CI run for the branch (excluding PR review events)."""
output = run_gh_command([
"run", "list",
"--branch", branch,
"--limit", "50",
"--json", "databaseId,status,conclusion,name,event,workflowName,createdAt",
])
runs = json.loads(output)
# Find first run that's a real CI run (pull_request or push), not skipped
for run in runs:
if run["event"] in ["pull_request", "push"]:
if run["conclusion"] not in ["skipped", None]:
return run
return None
def get_run_jobs(run_id: int) -> List[Dict[str, Any]]:
"""Get all jobs for a run."""
output = run_gh_command([
"run", "view", str(run_id),
"--json", "jobs",
])
data = json.loads(output)
return data.get("jobs", [])
def get_job_logs(run_id: int, job_id: int) -> str:
"""Get logs for a specific failed job."""
try:
return run_gh_command([
"run", "view", str(run_id),
"--job", str(job_id),
"--log-failed",
])
except subprocess.CalledProcessError:
return ""
def extract_error_lines(logs: str, max_lines: int = 20) -> List[str]:
"""Extract relevant error lines from logs."""
error_patterns = [
r"mise.*ERROR",
r"##\[error\]",
r"\[ERROR\]",
r"ERROR task failed",
r"Process completed with exit code",
r"\d+ tests, \d+ failures",
]
# Patterns to filter out (false positives)
ignore_patterns = [
r"shell: /usr/bin/bash",
r"fail-on-cache-miss",
r"Failed to save: Unable to reserve cache",
r"go: downloading",
r'"failed":\s*\d+,', # JSON field
r'"error":\s*\d+,', # JSON field
r"string error", # Proto definition
r"error_message", # Proto field
r"error_text", # Proto field
r"ErrorResponse", # Proto type
r"hint: given pattern matching", # Elixir warnings
]
error_lines = []
for line in logs.split("\n"):
# Check if line matches any error pattern
if any(re.search(pattern, line) for pattern in error_patterns):
# Skip if it matches ignore patterns
if not any(re.search(pattern, line) for pattern in ignore_patterns):
# Strip ANSI color codes and clean up
clean_line = re.sub(r'\x1b\[[0-9;]*m', '', line)
# Remove job prefix if present
clean_line = re.sub(r'^[^\t]*\t[^\t]*\t\d{4}-\d{2}-\d{2}T[\d:.]+Z\s*', '', clean_line)
error_lines.append(clean_line)
if len(error_lines) >= max_lines:
break
return error_lines
def main():
"""Main entry point."""
# Get current branch
branch = get_current_branch()
print(f"Branch: {branch}")
print()
# Find latest run
run = get_latest_ci_run(branch)
if not run:
print(f"No CI runs found for branch {branch}")
return
run_id = run["databaseId"]
conclusion = run["conclusion"]
workflow = run["workflowName"]
created_at = run["createdAt"]
print(f"Latest run: {workflow} (#{run_id})")
print(f"Status: {conclusion}")
print(f"Created: {created_at}")
print()
if conclusion == "success":
print("✓ Build passed!")
return
# Get failed jobs
jobs = get_run_jobs(run_id)
failed_jobs = [j for j in jobs if j.get("conclusion") == "failure"]
if not failed_jobs:
print("No failed jobs found (might be cancelled or timed out)")
return
print("=== Failed Jobs ===")
print()
for job in failed_jobs:
print(f" • {job['name']} (ID: {job['databaseId']})")
print()
print("=== Error Details ===")
print()
for job in failed_jobs:
job_id = job["databaseId"]
job_name = job["name"]
print("━" * 60)
print(f"Job: {job_name}")
print("━" * 60)
logs = get_job_logs(run_id, job_id)
error_lines = extract_error_lines(logs)
if error_lines:
for line in error_lines:
print(line)
else:
print("No clear error messages found in logs")
print()
if __name__ == "__main__":
main()
description context
Tools for interacting with GitHub Actions CI/CD workflows
fork

GitHub Actions Skill

This skill provides tools for working with GitHub Actions workflows, including checking build status, retrieving logs, and analyzing CI failures.

Available Scripts

get-build-failures.py

Finds the latest CI run for the current branch and extracts error messages from failed jobs.

Usage:

!`~/.claude/skills/github-actions/scripts/get-build-failures.py`

What it does:

  1. Detects the current git branch
  2. Finds the most recent CI run (pull_request or push events)
  3. Lists all failed jobs
  4. Extracts and displays relevant error messages from each failed job
  5. Filters out noise (false positives like proto definitions, cache warnings)

Output format:

  • Branch and run information
  • List of failed jobs with IDs
  • Error details for each failed job with context

Requirements:

  • gh CLI must be installed and authenticated
  • Must be run from within a git repository
  • Repository must have GitHub Actions workflows

Example output:

Branch: add-pyfunc-services

Latest run: Pull Request (#21157478597)
Status: failure
Created: 2026-01-20T02:33:35Z

=== Failed Jobs ===

  • Test LLM Optimize (ID: 60845052993)
  • Check Proto Generation (ID: 60845052998)

=== Error Details ===

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Job: Test LLM Optimize
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
mise ERROR no task //services/pyfunc-llm-optimize:python:check-format found
mise ERROR Run with --verbose or MISE_VERBOSE=1 for more information
##[error]Process completed with exit code 1.

When to Use This Skill

Use this skill when you need to:

  • Check if the latest CI build passed or failed
  • Investigate build failures without opening the GitHub web UI
  • Extract error messages from failed CI jobs
  • Gather build failure data for debugging in the main chat session

Design Philosophy

Scripts in this skill are data gatherers, not problem solvers:

  • They report what failed, not why it failed
  • They extract error messages with context
  • They don't attempt to diagnose issues or look at code
  • They provide clean output for the main chat session to analyze

Future Scripts

Potential additions to this skill:

  • cancel-workflow.py - Cancel running workflows
  • rerun-failed-jobs.py - Rerun only failed jobs
  • get-workflow-logs.py - Download logs for a specific workflow run
  • list-recent-runs.py - Show recent CI runs with status

Technical Details

Authentication

Scripts use the gh CLI which handles GitHub authentication automatically. Ensure gh auth login has been run.

Error Filtering

The get-build-failures.py script filters out common false positives:

  • Proto field definitions (error_message, ErrorResponse, etc.)
  • JSON fields in logs ("error": 7561)
  • Cache warnings
  • Dependency downloads
  • Elixir compiler hints

Run Selection

Scripts look for actual CI runs (pull_request or push events) and skip:

  • pull_request_review events (Claude Code bot actions)
  • pull_request_review_comment events
  • Skipped runs
  • Cancelled runs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment