fix-pr · diff
git:20261012.009563c to git:20261027.eff518c
2 added, 0 removed. Audit A to A.
---
name: fix-pr
description: Reads all unresolved review comments and threads on a change (PR/MR) — and its failing CI checks, which count as feedback too — implements the fixes, pushes, and replies to each thread. Tracker- and host-agnostic — GitHub via gh is the factory default; docs/agents/code-host.md overrides. Use when user says "fix pr comments", "address review", "/fix-pr", or wants to respond to PR review feedback.
---
# Fix PR
Reads review comments, implements fixes, pushes, replies to threads.
**Contract doc.** Change mechanics come from the repo's
`docs/agents/code-host.md` — read that file first if present. The commands
below are the **GitHub factory defaults** (`gh`), used verbatim when that doc is
absent or confirms GitHub; when it defines a different mechanic for an
operation (checkout, read feedback, reply, publish commits), the doc wins.
"PR" below means whatever the code host calls a reviewable change.
**Its annexes are deferred, not optional.** `code-host.md` links phase
annexes — `code-host-ci.md` above all — with the phase that opens each one
spelled out. **Do not read an annex at the start**: open it at the step that
names it, and not before. A worker that reads them all up front pays for the
whole contract in its first turn to use a quarter of it.
## Invoke
```
/fix-pr # fixes current branch PR
/fix-pr 42 # fixes PR #42
```
## Flow
### 1. Identify and check out the PR
Get the change metadata — GitHub default:
```bash
gh pr view <PR> --json number,title,headRefName,state
```
Refuse if PR is closed or merged.
If the current branch is not the PR branch (the /developer pipeline runs this
in a fresh worktree), first confirm **where you are**:
```bash
git rev-parse --path-format=absolute --git-dir --git-common-dir # two different paths = linked worktree
```
`--path-format=absolute` is not optional: without it git prints whichever form
is shortest from your cwd, so from a subdirectory of the primary checkout the
two answers differ (`/abs/path/.git` vs `../.git`) and the check reads a
primary checkout as a worktree.
As a /developer worker you must be in a linked worktree; if both paths are
equal you are in the user's primary checkout — do not check anything out,
end with `RESULT blocked reason=escaped worktree`. Then check out the
change per the code-host doc's fix-that-pushes checkout. GitHub default:
```bash
gh pr checkout <PR>
```
If that fails with `already used by worktree` (normal under /developer — the
PR branch is checked out in the build worker's worktree):
```bash
git fetch origin pull/<PR>/head:fix/pr-<PR>
git checkout fix/pr-<PR>
```
Two separate calls — the worktree sandbox refuses them joined with `&&`.
If `fix/pr-<PR>` is refused too — an earlier fix cycle's worktree still
holds it — use `fix/pr-<PR>-r2` (then `-r3`, and so on) in both commands.
Never invent a name outside `fix/pr-<PR>*`: it is what the pipeline's
cleanup matches. When the caller names the local branch instead —
/developer uses `agent/developer/fix-pr-<PR>` — use that name, with the same
`-r2`, `-r3` suffixes.
Push later with `git push origin HEAD:<pr-branch>` instead of a plain
push.
Never `git checkout main` — in a linked worktree it fails because `main` is
checked out in the primary worktree.
### 2. Read all feedback
Per the code-host doc's read-feedback operation. GitHub default:
```bash
# Top-level comments
gh pr view <PR> --comments
# Review threads (inline)
gh api repos/{owner}/{repo}/pulls/<PR>/comments \
--jq '[.[] | {id, path, line, body, in_reply_to_id}]'
# Review summaries
gh api repos/{owner}/{repo}/pulls/<PR>/reviews \
--jq '[.[] | select(.body != "") | {id, state, body}]'
```
Collect: unresolved inline threads, review summary comments, top-level PR comments.
**Red CI is feedback too.** The `/developer` pipeline dispatches a fix job for
a failing build as well as for a review, and a build that broke after a CLEAN
review has **no threads at all**. So before concluding there is nothing to act
on:
- If the task prompt named a failing job (`The PR's CI is red: <url>`), that
**is** your feedback — the failing checks are the work, whether or not any
thread exists.
- Otherwise, if you found no threads and no comments, read the change's checks
before giving up. **This is the step that opens `docs/agents/code-host-ci.md`**
(the code-host doc's CI annex) if the repo has one — take its "read the
checks" operation from there. GitHub default:
```bash
gh pr checks <PR> --json name,state,link --jq \
'[.[] | select(.state != "SUCCESS" and .state != "SKIPPED")]'
```
Refuse only when **all** of it comes back empty: no threads, no comments, and
either green checks or no CI. Then there is genuinely nothing to fix.
When the CI is what you are fixing, first check the failing job **actually
executed**, per the CI annex's classify-a-red operation (GitHub default:
`gh run view <run-id> --json jobs` — a failed job with zero steps never
started). A job the CI could not start (runner offline, minutes exhausted) is
not fixable from a worktree, and no amount of waiting turns it green: stop
and report `RESULT blocked reason=ci-infra <one line naming the cause>`
instead of waiting for it or re-running it. Otherwise, get the failure's
detail from the job itself (`gh run view --log-failed`, or the job URL)
rather than re-running the whole suite locally to reproduce it — you still
run the project's checks once after the fix, in step 3.
### 3. Implement fixes
- Address every unresolved comment, except those labelled `[note]`: a review
by review-pr labels each finding, and `[blocking]` and `[fix]` are both
work for you. A `[fix]` the summary lists without an inline thread is work
too. Leave `[note]` findings alone unless the task prompt asks for them
- Keep changes minimal — only what feedback requests
+ - A finding that names several locations is fixed at all of them; grep the
+ files you touch for further instances of the same pattern
- Do not refactor unrelated code
- Red before green, as in implement-issue: a behaviour you fix or add gets a
test you run and see fail first; a test you rewrite because it could not
fail must be seen failing against the broken behaviour before you keep it
- While iterating on a fix, run **only the test file covering it**, with the
project's quietest reporter: `pnpm test <path/to/the.test.ts> --reporter=dot`
(or `--silent`, per the project)
- After **all** fixes are applied — once, not per comment — run the project's
checks (see `AGENTS.md` / `CLAUDE.md` for the exact commands), typically:
```bash
pnpm typecheck
pnpm test --reporter=dot
```
Fix failures before committing; re-run just the failing file or test name to
see why, never the whole suite again.
Skip that run — and the lint gate below — when the repo's pre-push hook
(`lefthook.yml`, `.husky/pre-push`…) already runs the typecheck and the full
suite: the push in step 4 is that run, and running both pays for the same gate
twice on the same commit. If the hook rejects the push, fix what it names,
amend the commit, and push again.
Where the toolchain has no quiet reporter, judge the run by its exit code —
`<the command> > /tmp/check.log 2>&1; echo "exit=$?"`, grepping the log only
when it is non-zero. Never pipe it into `tail`: that discards the status and
costs you a re-run. A green gate is never re-run to confirm.
- Then run the project's **formatter in its writing form** (`cargo fmt --all`,
`biome check --write .`, `prettier -w`, `ruff format` — whatever this repo
uses) and its lint gate, before the commit. A fix pass that lands a
formatting failure sends the change round the loop again for something a
formatter fixes in a second, and this time the cycle is already spent.
### 4. Commit and push
```bash
git add <files you changed> # stage by path — never `git add -p` (interactive) or `git add -A`
git commit -m "fix(pr): address review comments on #<PR>"
git push origin <branch> # from a local fix/pr-<PR>: git push origin HEAD:<pr-branch>
```
Run the push bare, in a call of its own, with the Bash tool's maximum
timeout (`timeout: 600000`) so a gate-running pre-push hook finishes in the
foreground. No `> log; echo $?`, no `| tail`: the worktree sandbox refuses the
compound shapes, and a pipe throws the exit code away. The tool reports a
non-zero exit by itself; when the output is long, search the file the harness
saved it to, in a separate call, only on failure.
**Do not wait for CI after the push.** Watching the checks, re-running a red
job, or digging through its log is not part of this job: under /developer the
orchestrator's checks gate waits on that same CI before merging, re-runs a
flaky job once, and sends a new fix job if it stays red. In the field a fixer
spent 11 minutes watching CI and 15 calls on the log of a flaky run the
orchestrator would have re-run anyway. Push, reply, report.
### 5. Reply to threads
For each thread addressed, reply per the code-host doc. GitHub default:
```bash
gh api repos/{owner}/{repo}/pulls/<PR>/comments/<COMMENT_ID>/replies \
--method POST \
--field body="Fixed in <commit-sha>: <one-line description of what changed>."
```
A **CI-only** fix job has no threads to reply to — that is normal, not a
failure. Leave a record on the change instead, per the comment-on-a-change
operation, so the next reader knows why the commit exists (GitHub default:
`gh pr comment <PR> --body "Fixed the failing checks in <sha>: <what broke>."`).
### 6. Report
List each comment addressed and what was done. Flag any comment skipped and why.
## Rules
- One commit for all fixes (not one per comment)
- Reply to every thread you address — but a job with no threads (a red build
after a CLEAN review) is a valid job, not a reason to refuse
- If a comment is unclear: make the most reasonable interpretation, implement
it, and state your interpretation in the thread reply (unattended — there is
no one to ask). If a comment is wrong, say why in the reply instead of
implementing it
- If a fix would break tests: report it, do not force-push broken code
- Thread resolution follows the code-host doc: GitHub resolves on push
automatically (never resolve by hand); other hosts may need an explicit
resolve after the reply (GitLab does)