PR Review Fixer
Fetch unresolved review threads on a change request (CR), validate each issue, create a fix plan, implement fixes, and verify CI checks pass (tests, lint, build).
This skill is forge-aware: the repo may be on GitHub (gh) or GitLab (glab).
It does not hard-code either CLI — it follows the shared operation contract and the
adapter for whichever forge the repo uses.
Forge setup (do this first)
- Read
~/.claude/forge-adapters/CONTRACT.md— it defines the neutral terms (CR, thread, note,<id>) and the operations this workflow calls. - Run
PREFLIGHTto determineFORGEand confirm the CLI is authenticated. If not, stop and tell the user how to authenticate. - Read the matching adapter
~/.claude/forge-adapters/<FORGE>.md. Every concrete command below comes from that adapter's## OPERATIONsection — do not improvisegh/glabcommands.
Workflow
1. Fetch CR and Threads
CR_VIEW → keep `id` and `source_branch`
THREADS_FETCH <id> → normalised array, one element per thread (see CONTRACT.md):
{thread_id, scope, resolved, resolvable, author, path, line, body}
Because THREADS_FETCH returns the same shape on both forges, the rest of this
workflow is identical regardless of forge. Save the array to
/tmp/cr-review-${id}/threads.json.
2. Filter Threads
A thread is a "Claude review" if its body contains the sentinel
<!-- claude-local-review --> (emitted by the local-review agent). On neither
forge is there a bot author — notes post under the invoking user — so the sentinel
is the only reliable signal.
From the normalised array:
- Exclude resolved: drop elements where
resolved == true. - Claude review dedup: when several elements are Claude reviews, keep only the most recent (the adapter already collapsed each thread to its last note; here you dedup across threads if the same review was reposted).
- Bucket by
scope:diff(haspath/line) vscr-level. - Keep only actionable items: requested changes, questions, concrete suggestions. Skip pure acknowledgements, thanks, or informational notes.
3. Determine Working Location
Nothing from this skill is written into the repo. Review reports go out as CR comments; the rune task file lives in a system temp directory.
Working directory: /tmp/cr-review-${id}/. Create it before step 1. Never write
under the repo (no .claude/reviews/, no report files beside the code).
Iteration tracking: count prior overview comments via CR_NOTES_LIST whose body
contains the sentinel <!-- pr-review-overview -->. That count + 1 is the current
iteration N. The sentinel is required because notes post under your own account, so
an author check cannot find prior iterations.
N = (CR_NOTES_LIST <id> | count bodies containing "<!-- pr-review-overview -->") + 1
4. Validate Issues
Diff-anchored threads (scope: "diff"):
- Read the referenced code at
path:line. - Evaluate: is the issue still present? Is the suggestion correct? Does it align
with project conventions (consult the repo's
CLAUDE.md)? - Mark valid or invalid with a brief rationale.
CR-level threads (scope: "cr-level"):
- Parse the body for actionable items.
- Check the feedback still applies to the current CR state.
- Evaluate reasonableness and alignment with project goals.
- Mark valid or invalid; skip pure acknowledgements.
5. Prepare Review Overview
Assemble in-context (not on disk). Step 11 posts the final version — including CI status from step 9 — as a CR comment. Structure:
# CR Review Overview - Iteration [N]
**CR**: [id] | **Branch**: [name] | **Date**: [YYYY-MM-DD]
## Valid Issues
### Diff-Anchored Issues
#### Issue 1: [title]
- **File**: `path:line`
- **Reviewer**: @user
- **Comment**: [quoted]
- **Validation**: [rationale]
### CR-Level Issues
#### Issue 2: [title]
- **Reviewer**: @user
- **Comment**: [quoted]
- **Validation**: [rationale]
## Invalid/Skipped Issues
### Issue A: [title]
- **Location**: `path:line` or CR-level
- **Reason**: [why invalid]
6. Create Task List
Use rune to create the task file under the temp working directory from step 3 — never in the repo:
WORK_DIR="/tmp/cr-review-${id}"
mkdir -p "${WORK_DIR}"
TASK_FILE="${WORK_DIR}/review-fixes-${N}.md"
rune create "${TASK_FILE}" --title "CR Review Fixes - Iteration ${N}"
rune batch "${TASK_FILE}" --input '{
"operations": [
{"type": "add", "title": "Fix: [issue 1]"},
{"type": "add", "title": "Fix: [issue 2]"}
]
}'
7. Fix Issues
Loop through tasks:
rune next [file]— get next taskrune progress [file] [id]— mark in-progress- Implement the fix, following the project's existing patterns
rune complete [file] [id]— mark complete- Repeat until done
8. Check CI Status
CI_STATUS <id> → normalised array of {name, state}
If the array is empty, the CR has no CI configured — skip steps 8–9 entirely and
note "no CI to verify" in the overview. Otherwise handle every element whose state
is failed.
9. Fix CI Issues
For each failed check:
- Get logs:
CI_JOB_LOG <id> <name>. - Reproduce locally using the project's own commands. Prefer a
Makefiletarget if present (make test,make lint), otherwise the command documented in the repo'sCLAUDE.md/README (e.g.pytest,npm test,go test ./...,ruff). - Parse output; persist any captured logs to
${WORK_DIR}/ci-output.txt, never inside the repo. - Fix:
- Test failures: decide whether the test expectation is wrong or the code has a bug — never edit a test purely to make it pass.
- Lint/format: apply the project's auto-fix, then manual fixes.
- Type/build: fix the reported mismatches.
- Re-run locally to verify.
- Add to the task list if not already tracked.
10. Reply in Threads and Resolve
For every actionable thread that was processed (validated-and-fixed or marked invalid), post a short reply inside that thread — not a new CR-level note — stating the outcome:
- Fixed: one or two sentences naming the change (file, what was done).
- Invalid/skipped: the one-line rationale from step 4.
Stage each reply body in ${WORK_DIR}/reply-<n>.md, then:
THREAD_REPLY <id> <thread_id> ${WORK_DIR}/reply-<n>.md
Then resolve with THREAD_RESOLVE <thread_id>:
- Resolve threads whose issue was fixed.
- Resolve Claude-review threads (body carries the
<!-- claude-local-review -->sentinel) once every item in them is addressed — fixed or invalidated with rationale. They are machine-generated; nobody else will close them. - Leave unresolved human-authored threads whose finding was disputed or skipped — the human gets the in-thread reply and decides whether to close.
Forge specifics (see adapters): on GitLab, THREAD_REPLY works on an individual
(non-thread) note too — the reply converts it into a thread, after which
THREAD_RESOLVE works even though THREADS_FETCH reported it
resolvable: false. So reply first, then resolve. On GitHub, only diff-anchored
review threads accept in-thread replies; for CR-level comments the reply rides in
the step 11 overview instead (quote the original), and resolution is skipped.
11. Post Review Report as CR Comment
Compose the final overview (structure from step 5, plus CI status from step 9), stage
it in ${WORK_DIR}/overview-${N}.md, then:
CR_COMMENT <id> ${WORK_DIR}/overview-${N}.md
The body MUST begin with the <!-- pr-review-overview --> sentinel on its own line —
that is what the iteration counter in step 3 matches on. Without it, N will not
increment between rounds.
12. Commit, Push, and Verify
After all fixes:
- Run the full test suite locally (project command / Makefile target).
- Run the linter.
- Commit the code changes.
- Push to remote (
git push). - If CI exists, re-run
CI_STATUSto confirm checks pass; otherwise note there is no CI to wait on.
Nothing from /tmp/cr-review-${id}/ belongs in the commit — those paths are outside
the repo and git ignores them automatically.
Key Behaviors
- Forge-neutral: never call
gh/glabdirectly; go through the contract operations so the same workflow runs on GitHub and GitLab. - Auto-fix: fix all validated issues without pausing for approval.
- Convention adherence: follow the project's existing patterns and tooling.
- Deduplication: consolidate multiple notes on the same issue into one task.
- CI-aware but CI-optional: handle checks when they exist, no-op when
CI_STATUSis empty. - Local reproduction: run tests/linters locally before pushing fixes.
- Reports as comments: post review reports as CR comments, never to disk under the repo.
- Replies in-thread: outcomes for individual findings go into the originating
thread (
THREAD_REPLY) and handled threads get resolved, so the CR's open-thread count reflects what actually still needs attention. - No in-repo working files: rune task files and captured output live under
/tmp/cr-review-${id}/, not in the repo.