Fetch unresolved PR comments (both code-level and PR-level), validate issues, and fix them. Also checks CI status and fixes failing tests, lint errors, and build issues...
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.
~/.claude/forge-adapters/CONTRACT.md — it defines the neutral terms (CR,
thread, note, <id>) and the operations this workflow calls.PREFLIGHT to determine FORGE and confirm the CLI is authenticated. If
not, stop and tell the user how to authenticate.~/.claude/forge-adapters/<FORGE>.md. Every concrete
command below comes from that adapter's ## OPERATION section — do not improvise
gh/glab commands.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.
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:
resolved == true.scope: diff (has path/line) vs cr-level.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
Diff-anchored threads (scope: "diff"):
path:line.CLAUDE.md)?CR-level threads (scope: "cr-level"):
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]
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]"}
]
}'
Loop through tasks:
rune next [file] — get next taskrune progress [file] [id] — mark in-progressrune complete [file] [id] — mark completeCI_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.
For each failed check:
CI_JOB_LOG <id> <name>.Makefile
target if present (make test, make lint), otherwise the command documented in
the repo's CLAUDE.md/README (e.g. pytest, npm test, go test ./..., ruff).${WORK_DIR}/ci-output.txt, never
inside the repo.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:
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>:
<!-- 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.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.
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.
After all fixes:
git push).CI_STATUS to 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.
gh/glab directly; go through the contract
operations so the same workflow runs on GitHub and GitLab.CI_STATUS
is empty.THREAD_REPLY) and handled threads get resolved, so the CR's open-thread
count reflects what actually still needs attention./tmp/cr-review-${id}/, not in the repo.