CI Triage - turn a red pipeline into a verdict and a route
A red check is not automatically a code bug. The highest-value call in CI triage is the red-in-CI, green-locally delta: is this a real defect CI merely surfaced first, or an environment / pin / config / workflow failure that never touched the code? Route a config or runner failure to a code fix and you thrash on code that was never wrong. This is the single-chat form of the ci-failure-diagnoser seat - match the failure to a signature, make that call, and route it, all in the current context. It is the CI sibling of project-runtime-failure-signatures (a crash on your own machine) and runs on the superpowers:systematic-debugging method.
First: pull the right log, read the right line
- Confirm it is actually red before spending the triage -
gh pr checks; a re-run or a later push may already be green.
- Resolve the failing run from the PR head branch, then pull only the failed step:
gh run list --branch <head> --json databaseId,conclusion, then gh run view <id> --log-failed. When --log-failed is empty or unrevealing (a fatal warning promoted in an earlier green step is invisible to it), fall back to the full step log.
- Read the FIRST error, not the tail - the first
error CS#### / compile line is the signal; the Build FAILED block and the final count just restate it. On GitHub Actions the real error is often an ##[error] line inside a collapsed group, timestamp-prefixed and colorized - grep loose (a substring, not an exact string).
The signatures - what each means and where the fix lives
- Compile / restore. A CS/NG/TS compile error is code - fix it. A restore red is usually NOT the code: a lockfile out of sync with the manifest -
NU1004 under dotnet restore --locked-mode, npm ci refusing a package-lock.json that no longer matches package.json - so a locked restore fails where a loose install would silently update; an unresolvable version set (npm ERESOLVE on a peer conflict, ETARGET when no version matches the range); a private feed the runner cannot reach or auth to (NU1301 - the source, not the package); a package no configured source carries (NU1101 - a wrong id or version, or a missing source); a rate-limited registry; or a stale cache key replaying an old package set. Fix the lockfile or the feed, not the code.
- Green locally, red only on the runner (reach here first when your local run passed). Case-sensitivity - a Linux runner is case-sensitive, so an import whose casing differs from the file builds on mac/Windows and fails only in CI. A file present locally but never committed, so the clean checkout lacks it. A Debug-vs-Release gap (a Release-only analyzer, a DEBUG-conditional path). A missing CI-only secret or env var. Tool-version skew -
global.json rollForward, .nvmrc / engines, the JDK or Xcode the runner image pins, a floating vs SHA-pinned action. The fix is the workflow or a pin, not the code.
- Quality gate. The .NET gate is a build with warnings promoted to errors plus a formatter check; a red here is committed formatting drift or a newly-promoted analyzer, not a compile break. The fix is the code - and any instinct to silence the warning or downgrade the severity is the reward-hack to resist.
- Signing / release (mobile). An expired distribution cert or provisioning profile, a rotated store API key, a non-incrementing build number, a keystore-secret decode failure, or native built off a stale bundle because the sync step was skipped. Pipeline config, not app code - route the fix through the skill covering mobile build, signing, and store submission; a project without one fixes it from its own pipeline definition.
- Workflow-config drift. The YAML itself - a renamed job, a broken
needs: or matrix leg, a wrong working-directory, a bumped action. A red check whose log shows the tool never ran is config; it routes back to you, never to a code fix.
- Infra flake (name the non-determinism, never bare 'flaky'). Test-ordering or shared static state (passes isolated, fails in the full parallel run); real-clock or real-network timing (a real HTTP call, a real timer that needed fake time, an implicit wait); or infra (
exit 137 = OOM/SIGKILL, exit 143 = SIGTERM/timeout, disk-full, a container-pull blip). Proof is a re-run with no code change passing (gh run rerun --failed).
Execution modes
This catalogue is single-sourced: the ci-failure-diagnoser seat preloads this same file, so the
inline and seated forms never drift. Loaded in the MAIN session, run the triage HERE. The
read-only evidence-gatherer fan-out is judged from the run's shape - but dispatch is
explicit-only house-wide, so the seats never start on your own say-so: a calling flow that
already asked seats-or-inline (the gated investigation skill) has answered it - inherit, never
re-ask; otherwise, when the shape below warrants gatherers, put it through ONE AskUserQuestion
(gatherers recommended, the trigger named in the option's description; plain-text options
where the harness lacks the tool) and stay inline on an inline answer. A shape that stays
inline needs no ask:
- Dispatch gatherers (parallel, one per failing job) when any of these holds: more than one
job or matrix leg is red; the failed step's log is huge, or
--log-failed came back empty so
the full step log must be walked; the triage needs a comparison (first bad run vs last good)
or a local repro attempt alongside the log read.
- Stay inline when one job failed and its failed-step log is short - pull it and read it
here; a gatherer would cost more than it saves.
Example: a matrix run with three red legs - three gatherers at once, one per leg, each returning
the first real error line plus its step context. The digests come back; the code-vs-environment
call and the route stay in this session. Do NOT dispatch the diagnoser seat from this skill -
the signatures are already in context, so the seat would only duplicate them; the seat exists
for the orchestrated issue flow and direct @agent- calls, where it runs this same file in an
isolated context with the same gatherer fan-out. Loaded INSIDE the seat, this section is already satisfied - the seat
is the dispatched form.
Route it
State the verdict per failing job: the signature, the code-vs-environment call, and where it goes - a code defect to the fix (the matching build/test resolver in the flow, or fix it inline), an environment / pin / workflow delta back to you with the named delta, a proven flake to a re-run. Never route a config or runner failure to a code fix. If the evidence does not fit a signature, say so and pull the fuller log rather than force-fit one.
Worked example - the failed step's first error line reads:
error NU1301: Unable to load the service index for source https://pkgs.example.com/nuget/v3/index.json. Response status code does not indicate success: 401 (Unauthorized).
The three-line verdict it produces:
signature: restore - NU1301 against the private feed, 401
call: environment - the runner's feed credential expired; the code and lockfile never changed
route: fix the feed secret in the workflow, then re-run; no code fix
1---2name: project-ci-failure-signatures3description: Use when a CI pipeline or PR check goes red and you want to triage it yourself in the current chat - match the failure to a signature, make the call between a real code defect CI surfaced first and an environment / pin / config / workflow failure that never touched the code, and route it. The single-chat form of the ci-failure-diagnoser seat. Trigger on red CI, PR check failing, passes locally but fails in CI, NU1301, ERESOLVE, exit 137, workflow YAML broke, flaky pipeline. Not a crash on your own machine (that is project-runtime-failure-signatures), not authoring CI/CD (that is devops).4---56# CI Triage - turn a red pipeline into a verdict and a route78A red check is not automatically a code bug. The highest-value call in CI triage is the red-in-CI, green-locally delta: is this a real defect CI merely surfaced first, or an environment / pin / config / workflow failure that never touched the code? Route a config or runner failure to a code fix and you thrash on code that was never wrong. This is the single-chat form of the ci-failure-diagnoser seat - match the failure to a signature, make that call, and route it, all in the current context. It is the CI sibling of `project-runtime-failure-signatures` (a crash on your own machine) and runs on the `superpowers:systematic-debugging` method.910## First: pull the right log, read the right line1112- Confirm it is actually red before spending the triage - `gh pr checks`; a re-run or a later push may already be green.13- Resolve the failing run from the PR head branch, then pull only the failed step: `gh run list --branch <head> --json databaseId,conclusion`, then `gh run view <id> --log-failed`. When `--log-failed` is empty or unrevealing (a fatal warning promoted in an earlier green step is invisible to it), fall back to the full step log.14- Read the FIRST error, not the tail - the first `error CS####` / compile line is the signal; the `Build FAILED` block and the final count just restate it. On GitHub Actions the real error is often an `##[error]` line inside a collapsed group, timestamp-prefixed and colorized - grep loose (a substring, not an exact string).1516## The signatures - what each means and where the fix lives1718- **Compile / restore.** A CS/NG/TS compile error is code - fix it. A restore red is usually NOT the code: a lockfile out of sync with the manifest - `NU1004` under `dotnet restore --locked-mode`, `npm ci` refusing a `package-lock.json` that no longer matches `package.json` - so a locked restore fails where a loose install would silently update; an unresolvable version set (npm `ERESOLVE` on a peer conflict, `ETARGET` when no version matches the range); a private feed the runner cannot reach or auth to (`NU1301` - the source, not the package); a package no configured source carries (`NU1101` - a wrong id or version, or a missing source); a rate-limited registry; or a stale cache key replaying an old package set. Fix the lockfile or the feed, not the code.19- **Green locally, red only on the runner** (reach here first when your local run passed). Case-sensitivity - a Linux runner is case-sensitive, so an import whose casing differs from the file builds on mac/Windows and fails only in CI. A file present locally but never committed, so the clean checkout lacks it. A Debug-vs-Release gap (a Release-only analyzer, a DEBUG-conditional path). A missing CI-only secret or env var. Tool-version skew - `global.json` rollForward, `.nvmrc` / engines, the JDK or Xcode the runner image pins, a floating vs SHA-pinned action. The fix is the workflow or a pin, not the code.20- **Quality gate.** The .NET gate is a build with warnings promoted to errors plus a formatter check; a red here is committed formatting drift or a newly-promoted analyzer, not a compile break. The fix is the code - and any instinct to silence the warning or downgrade the severity is the reward-hack to resist.21- **Signing / release** (mobile). An expired distribution cert or provisioning profile, a rotated store API key, a non-incrementing build number, a keystore-secret decode failure, or native built off a stale bundle because the sync step was skipped. Pipeline config, not app code - route the fix through the skill covering mobile build, signing, and store submission; a project without one fixes it from its own pipeline definition.22- **Workflow-config drift.** The YAML itself - a renamed job, a broken `needs:` or matrix leg, a wrong working-directory, a bumped action. A red check whose log shows the tool never ran is config; it routes back to you, never to a code fix.23- **Infra flake** (name the non-determinism, never bare 'flaky'). Test-ordering or shared static state (passes isolated, fails in the full parallel run); real-clock or real-network timing (a real HTTP call, a real timer that needed fake time, an implicit wait); or infra (`exit 137` = OOM/SIGKILL, `exit 143` = SIGTERM/timeout, disk-full, a container-pull blip). Proof is a re-run with no code change passing (`gh run rerun --failed`).2425## Execution modes2627This catalogue is single-sourced: the ci-failure-diagnoser seat preloads this same file, so the28inline and seated forms never drift. Loaded in the MAIN session, run the triage HERE. The29read-only evidence-gatherer fan-out is judged from the run's shape - but dispatch is30explicit-only house-wide, so the seats never start on your own say-so: a calling flow that31already asked seats-or-inline (the gated investigation skill) has answered it - inherit, never32re-ask; otherwise, when the shape below warrants gatherers, put it through ONE AskUserQuestion33(gatherers recommended, the trigger named in the option's description; plain-text options34where the harness lacks the tool) and stay inline on an inline answer. A shape that stays35inline needs no ask:3637- **Dispatch gatherers** (parallel, one per failing job) when any of these holds: more than one38 job or matrix leg is red; the failed step's log is huge, or `--log-failed` came back empty so39 the full step log must be walked; the triage needs a comparison (first bad run vs last good)40 or a local repro attempt alongside the log read.41- **Stay inline** when one job failed and its failed-step log is short - pull it and read it42 here; a gatherer would cost more than it saves.4344Example: a matrix run with three red legs - three gatherers at once, one per leg, each returning45the first real error line plus its step context. The digests come back; the code-vs-environment46call and the route stay in this session. Do NOT dispatch the diagnoser seat from this skill -47the signatures are already in context, so the seat would only duplicate them; the seat exists48for the orchestrated issue flow and direct @agent- calls, where it runs this same file in an49isolated context with the same gatherer fan-out. Loaded INSIDE the seat, this section is already satisfied - the seat50is the dispatched form.5152## Route it5354State the verdict per failing job: the signature, the code-vs-environment call, and where it goes - a code defect to the fix (the matching build/test resolver in the flow, or fix it inline), an environment / pin / workflow delta back to you with the named delta, a proven flake to a re-run. Never route a config or runner failure to a code fix. If the evidence does not fit a signature, say so and pull the fuller log rather than force-fit one.5556Worked example - the failed step's first error line reads:5758```text59error NU1301: Unable to load the service index for source https://pkgs.example.com/nuget/v3/index.json. Response status code does not indicate success: 401 (Unauthorized).60```6162The three-line verdict it produces:6364```text65signature: restore - NU1301 against the private feed, 40166call: environment - the runner's feed credential expired; the code and lockfile never changed67route: fix the feed secret in the workflow, then re-run; no code fix68```