| name | ci-triage |
| description | Triage failed CI runs on a GitHub-Actions–driven repo — classify regression vs flake vs infra, maintain a single rolling `main-red` issue when main is broken, and point humans at the suspect commit. Use when a workflow fails on `main`, or when a human asks "is main red?", "why did CI fail on main?", "triage this workflow run", "classify this failure". Paired with the global `pr-lifecycle` skill (PR-side CI triage) and the `web-testing` skill (invoked for `e2e` failures). |
ci-triage
Shared logic for classifying a failed CI workflow run and recording the outcome. Used by humans (or Claude in an interactive session) when triaging a red main workflow or a red check on an open PR — the latter via the global pr-lifecycle skill, whose CI-triage section delegates the taxonomy here.
This skill owns the taxonomy, the de-dup rules for the main-red issue, and the issue template. Repo-specific reproduction steps and failure patterns live in the consumer repo's CLAUDE.md / <repo>-dev-process (read by pr-lifecycle), and e2e classification is delegated to web-testing.
Taxonomy
Every failure lands in exactly one bucket. Be explicit — "unclear" is not a bucket, needs-human is.
| Bucket | Signal | Default action |
|---|
regression | Reproduces deterministically on main at HEAD | File/update main-red, @-mention suspect author |
flake | Same workflow passed on the previous main commit without code change | Comment on the existing main-red issue if one is open; otherwise skip |
infra | Postgres service didn't come up, rustup 403, pnpm registry down | File/update main-red labelled infra; do not @-mention authors |
needs-human | Logs truncated, auth failed, classification genuinely ambiguous | Open a main-red issue with raw log excerpt, label needs-human |
Do not invent a fifth bucket. If the signal you see fits nothing above, it's needs-human.
Suspect commit identification
The GitHub workflow_run payload includes head_sha. That's the commit that triggered this run — on a push: main event, it is the merge commit.
- Fetch the commit via
mcp__github__get_commit.
- If the commit message matches
Merge pull request #N, the suspect PR is #N; its author is the suspect author.
- Otherwise (direct push to main, squash-merge), use the commit author directly.
Never blame more than one commit per failure. If the previous main commit's CI was also red, link that issue rather than opening a new one.
The rolling main-red issue
One open main-red issue at a time. If main is broken for three days, that's one issue accumulating comments — not twelve.
Before filing:
mcp__github__list_issues with labels: main-red, state: open.
- If one exists, append a comment:
Run # also failed. Workflow <name>, bucket <bucket>, suspect (#, @). .
- Only open a new issue if none is open. Title:
main is red: <workflow> (<bucket>). Labels: main-red, plus infra or needs-human if applicable.
When main goes green again (the next successful run on the same workflow), close the issue with a comment naming the green run id. This close step is manual.
Issue body template
**Workflow**: <workflow-name>
**Run**: <run-url>
**First failed step**: <step-name>
**Bucket**: <regression|flake|infra|needs-human>
**Suspect**: <sha-short> — <commit-subject> (PR #<n>, @<author>)
### Failure excerpt
<last 30 lines of the failing step, or the ripgrep-extracted error block>
### Reproduction
<one of:>
- Deterministic: `<exact command from the workflow yaml>`
- Flake: passed on <prev-sha-short>; rerun button: <run-url>/attempts/2
- Infra: <service name> — <symptom>
- Needs human: <why the logs are ambiguous>
### Next action
<one line — "revert #N and reland", "rerun", "fix <specific thing>", "human eyes">
Keep the excerpt tight. Dumping the full log helps nobody.
Reproducing locally
Someone invoking this skill via the global pr-lifecycle skill should reproduce before filing, using the repo's check gate (named in its CLAUDE.md / <repo>-dev-process). For a main failure caught from outside a PR, check out main at the suspect SHA and run the same commands locally before filing.
For e2e failures specifically, delegate classification to web-testing's triage mode — it handles regression-vs-flake for browser-driven tests (the ambiguous case).
Log access
WebFetch cannot read authenticated GitHub Actions logs (403). The GitHub MCP gives you:
mcp__github__pull_request_read with method: get_check_runs — step names, status, timings (no log body).
- The workflow run's
jobs_url via mcp__github__get_commit + check_suite traversal — same metadata.
Log bodies are not reliably accessible from the GitHub MCP. When the log body is unavailable, classify from step names + exit codes + the workflow yaml, and bias toward needs-human rather than guessing.
Flake-detection heuristic
A failure is flake only if both hold:
- The same workflow on the previous main commit passed (check via
mcp__github__list_commits + check runs on the prior sha).
- The failing step's logs do not contain a symbol name, file path, or assertion message that appears in the suspect commit's diff.
One of those alone is not enough. A deterministic regression can pass on the prior commit; a real flake can mention a touched file by coincidence.
Constraints
- Never open a PR from this skill. Triage is read-only on the codebase.
- Never @-mention for
infra or flake buckets — alert fatigue kills the signal.
- Never close a
main-red issue without a green run id to cite.
- Scope: any GitHub-Actions–driven repo. The taxonomy and rolling
main-red pattern are repo-agnostic; the consumer repo's CLAUDE.md can override triggers and label conventions.
Relationship to other surfaces
| Surface | Role |
|---|
pr-lifecycle (global) | Interactive caller; its CI-triage section delegates the taxonomy here when triaging a red PR check. |
web-testing | Delegated to for e2e workflow classification. |