blamechris

/fix-ci

"Diagnose CI failures or cancellations on a PR, take corrective action (re-trigger, fix, or escalate), and report status."

blamechris 7 1 Updated 4mo ago
GitHub

Install

npx skillscat add blamechris/chroxy/claude-skills-fix-ci

Install via the SkillsCat registry.

SKILL.md

/fix-ci

Diagnose CI failures or cancellations on a PR, take corrective action (re-trigger, fix, or escalate), and report status.

Arguments

  • $ARGUMENTS - PR number (optional, defaults to current branch's PR)

Instructions

0. Gather CI State

PR_NUM=${1:-$(gh pr view --json number -q .number)}
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
BRANCH=$(gh pr view ${PR_NUM} --json headRefName -q .headRefName)
HEAD_SHA=$(gh pr view ${PR_NUM} --json headRefOid -q .headRefOid)

echo "PR: #${PR_NUM} | Branch: ${BRANCH} | HEAD: ${HEAD_SHA}"

# Get the latest CI run(s) for this branch
# Chroxy uses Copilot review + CodeQL ruleset
gh run list --branch ${BRANCH} --workflow "Copilot review" --limit 5 --json databaseId,status,conclusion,headSha,event,createdAt

1. Get Job-Level Status

For the most recent run matching HEAD_SHA:

RUN_ID=<id from step 0>
gh run view ${RUN_ID} --json jobs --jq '.jobs[] | {name, status, conclusion}'

Count jobs by status: passed, failed, cancelled, skipped, in_progress.

2. Classify Overall State

Apply these rules in order (first match wins):

Classification Condition Action
ALL_PASS Latest run's SHA matches HEAD AND all required jobs passed Report green, exit
IN_PROGRESS Any job still running or queued Poll until complete (see Step 2a)
STALE Latest run's SHA does NOT match HEAD Check for newer run, or retrigger
CANCELLED One or more jobs cancelled, none failed Check for replacement run (see Step 2b)
FAILED One or more jobs failed Per-job diagnosis (Step 3)

2a. IN_PROGRESS โ€” Poll for Completion

# Chroxy CI typically completes within 5 minutes
MAX_WAIT=300  # seconds
INTERVAL=30
ELAPSED=0

while [ $ELAPSED -lt $MAX_WAIT ]; do
  STATUS=$(gh run view ${RUN_ID} --json status -q .status)
  [ "$STATUS" = "completed" ] && break
  sleep $INTERVAL
  ELAPSED=$((ELAPSED + INTERVAL))
  echo "Waiting for CI... ${ELAPSED}s / ${MAX_WAIT}s"
done

# Re-classify after completion

2b. CANCELLED โ€” Check for Replacement Run

Cancellation often happens due to concurrency groups (a newer push cancels the older run). Check if a newer run exists for the same SHA:

# Check all runs for this SHA
gh run list --branch ${BRANCH} --workflow "Copilot review" --limit 5 --json databaseId,status,conclusion,headSha,event \
  | jq --arg sha "$HEAD_SHA" '[.[] | select(.headSha == $sha)]'

Decision logic:

  • If a newer completed successful run exists for the same SHA โ†’ ALL_PASS, report green
  • If a newer run exists but only ran partial jobs (e.g., different event trigger) โ†’ the full suite never ran, needs RETRIGGER
  • If no newer run exists โ†’ needs RETRIGGER

Common cause: CI concurrency groups with cancel-in-progress: true. When fix commits are pushed, the new pull_request event cancels the in-progress run. If the new event is a different type (e.g., pull_request_review which only triggers notification jobs), the full CI suite never re-runs.

3. Per-Job Diagnosis (for FAILED or CANCELLED jobs)

For each non-passing job, fetch logs and diagnose:

# For failed jobs โ€” get the failure logs
gh run view ${RUN_ID} --log-failed 2>&1 | tail -100

# For cancelled jobs โ€” limited logs available, check annotations
gh api repos/${REPO}/actions/runs/${RUN_ID}/jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, steps: [.steps[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion}]}'

Pattern match against known failures:

Chroxy-specific patterns:

  • npm test failures in server tests โ†’ check for missing test fixtures or mock setup issues
  • npx tsc --noEmit failures in app โ†’ TypeScript strict mode violations (check for any types, platform-specific issues)
  • npm run build -w @chroxy/dashboard failures โ†’ Vite build errors, missing CSS, or theme issues
  • npx jest failures in dashboard tests โ†’ component rendering or mock issues
  • WebSocket protocol test failures โ†’ check WS message format or auth flow issues

Generic patterns (apply to all repos):

  • rate limit / API rate limit exceeded โ†’ RETRIGGER (transient)
  • timeout / timed out / deadline exceeded โ†’ RETRIGGER (transient, unless recurring)
  • connection refused / network / ECONNRESET โ†’ RETRIGGER (transient)
  • permission denied / 403 / Resource not accessible โ†’ ESCALATE (permissions issue)
  • out of disk space / no space left on device โ†’ ESCALATE (infrastructure)
  • cancelled (no failure, just cancelled) โ†’ RETRIGGER

Classify each job into exactly ONE outcome:

Outcome When Action
RETRIGGER Cancelled by concurrency, transient error (network, timeout, rate limit) gh run rerun
FIX Known code issue with clear automated fix Commit fix, push (triggers new CI)
ESCALATE Unknown failure, permissions, infrastructure, or needs human judgment Report diagnosis + log excerpt

4. Take Action

RETRIGGER

# Preferred: re-run only failed/cancelled jobs (fast, targeted)
gh run rerun ${RUN_ID} --failed

# Fallback: close and reopen PR to trigger full CI suite
gh pr close ${PR_NUM}
gh pr reopen ${PR_NUM}

One retrigger attempt only. If the re-run also fails, escalate instead of retrying.

FIX

# 1. Check out the PR branch
git checkout ${BRANCH}

# 2. Make the fix (specific to the failure pattern matched)
# 3. Commit with descriptive message
git add <files>
git commit -m "fix(ci): Description of fix"

# 4. Push (triggers new CI automatically)
git push

ESCALATE

Do NOT take automated action. Report the diagnosis to the user:

## CI Escalation: PR #${PR_NUM}

**Job:** [job name]
**Status:** [failed/cancelled]
**Diagnosis:** [what the logs indicate]

**Log excerpt:**

[relevant log lines โ€” keep under 30 lines]


**Suggested next steps:**
- [specific recommendation based on diagnosis]

5. Wait for CI (if action taken)

After RETRIGGER or FIX, wait for the new run to complete:

# Chroxy CI typically completes within 5 minutes
MAX_WAIT=300
INTERVAL=30
ELAPSED=0

# Get the new run ID
sleep 10  # brief delay for run to appear
NEW_RUN_ID=$(gh run list --branch ${BRANCH} --workflow "Copilot review" --limit 1 --json databaseId -q '.[0].databaseId')

while [ $ELAPSED -lt $MAX_WAIT ]; do
  STATUS=$(gh run view ${NEW_RUN_ID} --json status -q .status)
  [ "$STATUS" = "completed" ] && break
  sleep $INTERVAL
  ELAPSED=$((ELAPSED + INTERVAL))
  echo "Waiting for CI... ${ELAPSED}s / ${MAX_WAIT}s"
done

# Check final result
CONCLUSION=$(gh run view ${NEW_RUN_ID} --json conclusion -q .conclusion)
echo "CI result: ${CONCLUSION}"

If the new run also fails, re-classify and escalate. Do NOT retrigger a second time.

6. Summary Report

## CI Status: PR #${PR_NUM}

| Job | Status | Action Taken |
|-----|--------|--------------|
| Job 1 | pass | โ€” |
| Job 2 | pass (after retrigger) | Retriggered: cancelled by concurrency |
| Job 3 | pass | โ€” |

**Result:** All jobs passing / N jobs need attention
**SHA:** ${HEAD_SHA}
**Run:** [link to run]

For the user-facing output, use a one-line summary table:

| PR | CI Status | Jobs | Action Taken |
|----|-----------|------|--------------|
| #XX | PASS (after retrigger) | 6/6 passed | Retriggered: Run Tests cancelled by concurrency |

Decision Tree

Start
  โ”‚
  โ”œโ”€ Get latest CI run for HEAD SHA
  โ”‚
  โ”œโ”€ Any run for this SHA?
  โ”‚   โ”œโ”€ NO โ†’ retrigger (stale)
  โ”‚   โ””โ”€ YES โ†“
  โ”‚
  โ”œโ”€ Run status?
  โ”‚   โ”œโ”€ in_progress โ†’ poll until complete, re-classify
  โ”‚   โ”œโ”€ completed โ†“
  โ”‚   โ””โ”€ other โ†’ investigate
  โ”‚
  โ”œโ”€ All required jobs passed?
  โ”‚   โ”œโ”€ YES โ†’ report green, exit
  โ”‚   โ””โ”€ NO โ†“
  โ”‚
  โ”œโ”€ For each non-passing job:
  โ”‚   โ”œโ”€ cancelled (no failure)?
  โ”‚   โ”‚   โ”œโ”€ Replacement run exists with full suite? โ†’ use that run
  โ”‚   โ”‚   โ””โ”€ No replacement โ†’ RETRIGGER
  โ”‚   โ”œโ”€ failed โ†’ fetch logs, pattern match
  โ”‚   โ”‚   โ”œโ”€ Known fixable pattern โ†’ FIX
  โ”‚   โ”‚   โ”œโ”€ Transient error โ†’ RETRIGGER
  โ”‚   โ”‚   โ””โ”€ Unknown/infrastructure โ†’ ESCALATE
  โ”‚   โ””โ”€ skipped โ†’ check if required (skip if not)
  โ”‚
  โ”œโ”€ Execute actions (retrigger / fix / escalate)
  โ”‚
  โ”œโ”€ If action taken โ†’ wait for new CI run
  โ”‚   โ”œโ”€ Passes โ†’ report green
  โ”‚   โ””โ”€ Fails again โ†’ ESCALATE (no second retrigger)
  โ”‚
  โ””โ”€ Report summary

Critical Rules

  1. Diagnose before acting โ€” Never blindly retrigger. Always check logs and classify the failure first.
  2. One retrigger attempt โ€” If the re-run also fails, escalate. Infinite retry loops waste time.
  3. SHA awareness โ€” Always verify the CI run matches HEAD. Stale runs on old SHAs are meaningless.
  4. Log excerpts in escalations โ€” Never escalate with just "CI failed." Include relevant log lines so the user can act without re-investigating.
  5. Minimal fix scope โ€” FIX actions should be surgical. Don't refactor code; just fix the CI failure.
  6. Composable โ€” Works standalone (/fix-ci 42) or from /full-review (Phase 2.5).
  7. Idempotent โ€” Safe to re-run. If CI is already green, reports success and exits.
  8. No attribution โ€” Follow project attribution policy in all commits.