| name | fix-pr-checks |
| description | Fix the currently-failing status checks on the current branch's PR in one sweep — route each failing check to the right diagnosis skill (via a Haiku subagent, escalating to Opus on a repeat failure), apply and verify the fix, commit, and push once — then return a summary. Use standalone when CI is red, or as the check-fixing step invoked by /watch-pr. Repo is adamayoung/TMDb. |
Fix PR Checks
Get a pull request's failing checks green: for each failing status check,
diagnose the cause, apply and verify a fix, and push. This is a single sweep
over the checks failing right now — it diagnoses and fixes them, pushes once,
and returns. Waiting on pending checks and deciding whether to sweep again belong
to the caller (you, or /watch-pr).
Repo is adamayoung/TMDb. GitHub reads use the GitHub MCP (mcp__github__*);
gh is authenticated and used for the blocking CI wait. If an MCP call fails with
401/403 (PAT expired or missing scope), fall back to the equivalent gh command.
It stands on the diagnosis skills
Don't read CI logs yourself. The analysis is already a capability — delegate it to
a Haiku subagent running the matching diagnosis skill (a repeat failure
re-diagnoses on Opus — see §2), so raw logs never enter your context and you
get back a file:line cause and a concrete fix:
- The Integration check (live-API suite from
integration.yml) →
/diagnose-integration-failure
- Any CI check — lint, markdown lint, build, or unit tests from
ci.yml →
/diagnose-ci-failure
This skill is the act-on-it layer: route → fix → verify → commit → push.
Principles
- One sweep, then return. Handle every check failing at invocation, push
once, report. Don't loop waiting for the re-run — that convergence is the
caller's job.
- Diagnose via Haiku, escalate a repeat to Opus, fix locally. Get Cause/Fix
from the diagnosis skill, then apply it and verify before claiming it
fixed with the delegated build/test skills. First diagnosis of a check runs
on Haiku; a check that comes back after a fix re-diagnoses on Opus (§2).
- Respect the attempt cap. A check gets at most 3 fix→push attempts
(tracked in the run ledger). If it still fails on the same root cause, stop
touching it and report it exhausted — never loop forever.
- Failing only. Act on
fail checks. pending checks are not yours to
chase — report them and leave waiting to the caller. claude-review and other
neutral checks are non-blocking.
- Never fake green. Don't edit
.github/workflows/* or CI config to silence a
check, and don't force-push, without surfacing to the user first.
0. Find the PR
Find the open PR for the current branch with mcp__github__list_pull_requests
(owner/repo from the origin remote, head: <owner>:<branch>, state: open), or
read a specific one with mcp__github__pull_request_read method get. Take
number/html_url/state/head.ref from the result.
- An explicit PR number in the arguments overrides the current branch.
- No PR for the current branch → stop and tell the user (suggest
/pr).
- State not
open → stop and report.
1. List the checks
Use mcp__github__pull_request_read method get_check_runs (owner/repo from
origin, pullNumber: <n>) — the individual CI/CD check runs for the head
commit. Classify each by status + conclusion:
- fix it (below):
conclusion is failure / timed_out / cancelled /
action_required.
- pending (report, leave for the caller to wait on):
status is not
completed (queued / in_progress).
- ignore:
conclusion is success / skipped / neutral (e.g.
claude-review).
If nothing is failing, return immediately (note any pending checks).
2. Diagnose each failing check (Haiku first, Opus on a repeat)
For each fail check under its attempt cap, spawn the check-diagnoser
agent (Agent tool, subagent_type: check-diagnoser — its no-fix/no-build
contract, report shape, and Haiku floor-pin live in
.claude/agents/check-diagnoser.md), substituting the check name and the
routed skill. Pick the model from the check's ledger history and pass it
explicitly (a call-site model beats the agent's frontmatter pin —
ADR-0014):
- First attempt →
model: haiku.
- Repeat (the ledger shows a prior attempt for this check — a fix that
didn't verify, or the check failed again after the push) →
model: opus,
prepending one line of prior-attempt context to the prompt: the previous
Cause/Fix and why it didn't stick. A misdiagnosis costs a full
fix→push→CI round trip; don't pay it twice on the same model tier.
Registry lag. A freshly merged agent file may not register as a
spawnable subagent_type until a new session
(knowledge/delivery-retros.md), and an unrecognised type silently starts a
plain general-purpose agent — with no contract at all now that the prompt
below carries none. If check-diagnoser is not a recognised type, don't
fall through silently: say so, and re-spawn as general-purpose with
.claude/agents/check-diagnoser.md read into the prompt as its contract.
The `<CHECK NAME>` check failed on the TMDb PR for branch `<branch>`.
Use the `<SKILL>` skill to diagnose it.
Two or more failing checks → one Workflow
One failing check stays a direct Agent call; a Workflow for a single agent is
pure overhead. At two or more, fan out instead — one diagnosis agent per
check, each schema-validated, with model set per agent from the ledger
history so the Haiku→Opus escalation survives (a Workflow cannot see that
history on its own — pass it in via args).
Run the script below via the Workflow tool with
args: { branch, checks: [{ name, skill, priorAttempt }] } — priorAttempt is
the ledger's record for that check (omit on a first attempt). It is embedded
here, not a file, because it runs once per invocation — the same rule that
keeps /review-plan's and /review-changes's scripts embedded, while
deliver-panel.js lives in .claude/workflows/ (it runs at every panel
decision point — seven of them — in a single run).
Invoking this skill is itself the opt-in to call Workflow.
The guards are executable throws, not instructions: a malformed payload
must fail at agent zero, never mid-fan-out.
export const meta = {
name: 'fix-pr-checks-diagnose',
description: 'Diagnose each failing PR check in parallel, one read-only agent per check',
phases: [{ title: 'Diagnose', detail: 'one agent per failing check' }],
}
const input = typeof args === 'string' ? JSON.parse(args) : args
if (!input || typeof input !== 'object') {
throw new Error('fix-pr-checks: args must be an object (or a JSON string encoding one).')
}
if (!Array.isArray(input.checks)) {
throw new Error(
`fix-pr-checks: args.checks must be an ARRAY, got ${typeof input.checks}. ` +
`Refusing to fan out — iterating a non-array here is the ~281-agent failure.`
)
}
if (!input.checks.length) throw new Error('fix-pr-checks: args.checks is empty — nothing to diagnose.')
(!input..( c && c === &&
c. === && c. && c. === && c.)) {
(
+
)
}
( input. !== || !input.) {
()
}
= {
: ,
: ,
: {
: { : },
: { : , : },
: { : , : },
: { : , : },
: { : , : },
: {
: ,
:
+
,
},
},
: [, , , ],
}
()
results = (input..(
(
+
+
(c.
? +
: ),
{
: ,
: ,
: ,
: c. ? : ,
: ,
}
).( r && { ...r, : c. })
))
live = results.()
dead = input..( !live.( r. === c.)).( c.)
(dead.) ()
{ : live, : dead }
The fix stays here. Only diagnosis is delegated; §3's apply → verify →
commit and §4's single batched push run in the conductor, which owns the build
slot and the attempt counters. A check in undiagnosed is not clean — leave
it and report it.
3. Apply, verify, commit
From the returned Cause/Fix:
- Apply the fix to the offending
file:line.
- Verify locally with the matching delegated skill —
/lint, /build,
/test, or /integration-test (they run in Haiku subagents; logs stay out of
context).
- Commit with a gitmoji message; record the SHA. Batch the sweep's
pushes — commit per fix, but
git push once after all failing checks are
handled (§4), so the re-run covers everything in one CI cycle.
- Increment that check's attempt counter in the ledger.
If the diagnosis is ambiguous or the fix is risky, don't guess — leave the check,
note it for the user, and move on.
4. Push once, then finish
After all failing checks are handled, if you committed any fixes, git push once
(it re-triggers CI — expected). There is no pre-commit hook (.git/hooks/
holds only *.sample), so nothing lints on your behalf — run /lint before
committing, or a lint fix can itself land a lint failure.
Return: sweep summary
Report, concisely:
- Fixed — check name → cause (
file:line) → commit SHA.
- Exhausted — checks at the 3-attempt cap still failing on the same root
cause; stop and hand to the user.
- Skipped/ambiguous — failing checks you intentionally left (risky/unclear),
with why.
- Pending — checks still in progress, left for the caller to wait on.
- Whether you pushed (and thus re-triggered CI) so the caller can decide whether
to sweep again.
Arguments: $ARGUMENTS