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.
Scanned 9/20/2026
npx -y skills add adamayoung/TMDb --skill fix-pr-checks --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Fix Pr Checks?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/adamayoung-fix-pr-checks)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
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
1. **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.
2. **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).
3. **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.
4. **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.
5. **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.
```text
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 `throw`s, not instructions**: a malformed payload
must fail at agent zero, never mid-fan-out.
```javascript
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' }],
}
// `args` can arrive as a JSON STRING rather than an object (a known harness
// gotcha). Parsing is not optional: iterating a string character-by-character
// once fanned out ~281 agents into a spend limit, so validate the SHAPE before
// spawning anything.
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.')
// Element shape too, not just the container. `checks: ['CI', 'Integration']` is the
// natural malformed form: it passes an Array.isArray check, then spawns agents whose
// prompts read "the `undefined` check failed" and reports full coverage, because the
// dead-detection compares `undefined === undefined`. A `null` element instead throws
// at the dead-detection — AFTER the agents ran, discarding their work. Both violate
// "fail at agent zero", so validate here.
if (!input.checks.every((c) => c && typeof c === 'object' &&
typeof c.name === 'string' && c.name && typeof c.skill === 'string' && c.skill)) {
throw new Error(
'fix-pr-checks: every entry in args.checks must be an object { name, skill } with ' +
'non-empty strings. Refusing to fan out on a malformed element.'
)
}
if (typeof input.branch !== 'string' || !input.branch) {
throw new Error('fix-pr-checks: args.branch must be a non-empty string.')
}
const DIAGNOSIS_SCHEMA = {
type: 'object',
additionalProperties: false,
properties: {
check: { type: 'string' },
summary: { type: 'string', description: 'one or two sentences on what failed' },
likelyCause: { type: 'string', description: 'the ranked root cause' },
suggestedFix: { type: 'string', description: 'the concrete next step' },
fileLine: { type: 'string', description: 'the offending file:line, or "unknown"' },
observed: {
type: 'string',
description:
'for an Integration check: the live call made and the shape returned, or "unavailable (headless)"; ' +
'omit for CI checks. NEVER include a URL, command or header carrying TMDB_API_KEY.',
},
},
required: ['check', 'summary', 'likelyCause', 'suggestedFix'],
}
phase('Diagnose')
const results = await parallel(input.checks.map((c) => () =>
agent(
`The \`${c.name}\` check failed on the TMDb PR for branch \`${input.branch}\`.\n\n` +
`Use the \`${c.skill}\` skill to diagnose it.` +
(c.priorAttempt
? `\n\nTHIS IS A REPEAT. The previous attempt did not stick:\n${c.priorAttempt}\n` +
`Do not re-propose it — find what that diagnosis missed.`
: ''),
{
label: `diagnose:${c.name}`,
phase: 'Diagnose',
// The agent definition carries the no-build rule and report contract.
agentType: 'check-diagnoser',
// Haiku first; a repeat escalates. The conductor supplies the history —
// a Workflow cannot see the ledger's per-check attempt counters.
model: c.priorAttempt ? 'opus' : 'haiku',
schema: DIAGNOSIS_SCHEMA,
}
).then((r) => r && { ...r, check: c.name })
))
const live = results.filter(Boolean)
const dead = input.checks.filter((c) => !live.some((r) => r.check === c.name)).map((c) => c.name)
if (dead.length) log(`WARNING: no diagnosis returned for ${dead.join(', ')} — treat as undiagnosed, not as clean`)
return { diagnoses: live, undiagnosed: 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:
1. **Apply the fix** to the offending `file:line`.
2. **Verify locally** with the matching delegated skill — `/lint`, `/build`,
`/test`, or `/integration-test` (they run in Haiku subagents; logs stay out of
context).
3. **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.
4. 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
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!