Multi-Review
Orchestrator skill that gets four independent perspectives on a branch's changes and helps you act on the findings. Lives on top of /review and /code-review, not a replacement for either.
The skill is the brain. The bash workhorse is at scripts/multi-review.sh — it spawns the subprocesses and produces a structured merged review. The skill handles intent, scope resolution, and the post-review walkthrough.
Cost note
Four concurrent Opus 4.7 sessions plus a merger is ~5× the spend of a single /review or /code-review. Mention this once to the user at the start if they didn't invoke with an explicit PR number (signals an exploratory run).
Pipeline
1. Pre-flight
Hard-fail with a clear message and stop if any of these are true:
- Not in a git repo
claudeCLI not onPATHghCLI not onPATH/reviewor/code-reviewskills not discoverable (check.claude/skills/and the user's plugin marketplace)- Current branch equals the base branch
Detect — never trust defaults blindly:
- Repo: parse
owner/repofromgit remote get-url origin. Theghdefault repo can disagree (fork-vs-upstream); always derive from git. Pass this asGH_REPOto the script. - Base branch:
git symbolic-ref refs/remotes/origin/HEAD --short→ striporigin/. Fall back tomain. Pass asBASE_BRANCH. - PR number (reviewer mode only — see step 2): priority chain:
- Positional argument:
/multi-review 272 gh pr view --repo OWNER/REPO --json number,state --jq 'select(.state=="OPEN").number'AskUserQuestion: "Which PR number?"
- Positional argument:
Auto-resolve silently — don't ask, just decide and log a one-liner in the chat:
- Dirty tree: review committed only.
- No PR exists (developer mode is fine without one; reviewer mode must have one): only
/code-reviewagents will run. - Stale base: run
git fetch origin <base>once before continuing.
The principle: prompts cost user attention. Only ask on genuine ambiguity where the wrong default would produce a misleading review.
2. Mode selection
Ask via AskUserQuestion (exactly one question, two options):
Are you reviewing your own changes or someone else's?
- My own changes — fix findings locally before opening or updating the PR.
- Someone else's PR — post findings back as inline + body comments.
Save the choice as MODE (developer | reviewer).
If reviewer and no PR is detectable after step 1, prompt for the PR number now.
If developer and the current branch == base, that already hard-failed in step 1.
3. Run the multi-review script
Compute the run directory name:
- Reviewer mode:
pr-<N>-<branch-slug>-<timestamp> - Developer mode:
<branch-slug>-<timestamp>
Where:
<branch-slug>= current branch name with/→-and other shell-unsafe chars stripped ([^A-Za-z0-9._-]→-).<timestamp>=date +%Y%m%d-%H%M%S.
Then invoke the script (run directory is the first positional arg, the rest are env vars):
RUN_DIR="reviews/$RUN_NAME"
PR_NUMBER="$PR_NUMBER" \
GH_REPO="$GH_REPO" \
BASE_BRANCH="$BASE_BRANCH" \
MODEL="claude-opus-4-7" \
bash .claude/skills/multi-review/scripts/multi-review.sh "$RUN_DIR"
The script handles parallel spawn, wait, merger. It produces:
$RUN_DIR/review-skill-{1,2}.md(when PR_NUMBER is set)$RUN_DIR/code-review-{1,2}.md$RUN_DIR/merged-review.md(human-readable)$RUN_DIR/merged-review.json(machine-readable — the walkthrough's input)$RUN_DIR/*.log
Stream a one-line "running 4 reviewers + merger, expect a few minutes" to the user. Don't poll; the script blocks until done.
4. Parse merged-review.json
Read $RUN_DIR/merged-review.json. Schema:
{
"summary": "one paragraph synthesis",
"findings": [
{
"id": 1,
"severity": "Blocker | Concern | Nit | Info | Trivial",
"title": "short title",
"file": "path/to/file.go",
"line": 30,
"body": "the finding text",
"suggestion": "concrete fix text (may be empty)",
"reviewer_agreement": "3/4"
}
],
"positives": ["consensus positive observations, prose"],
"verdict": "Approve | Approve with nits | Request changes | Block"
}
Filter for the walkthrough: keep severity in {Blocker, Concern, Nit}. Drop Info and Trivial and the positives.
Sort: by severity ascending (Blocker → Concern → Nit), then by reviewer_agreement descending within a tier.
Summarize the dropped items as a one-paragraph chat preamble before starting the walkthrough — e.g., "Skipping 3 Info/Trivial findings (perf observations, allocation note). 2 positives noted: empty-string fix is a real bug, perf win on isHex. Walking 5 actionable findings."
Display format for findings (used by both walkthrough modes)
Each finding is shown to the user in the chat the same way it appears in merged-review.md — so the walkthrough reads like an interactive version of the file. Use this template:
─────────────────────────────────────────────
Finding <id>/<total> · <Severity>
<title>
Reviewer agreement: <N/M> · Sources: </review>, </code-review>
File: <file>:<line> (omit this line if file is null)
<body>
Suggestion:
<suggestion> (omit this block if suggestion is empty)
─────────────────────────────────────────────
For reviewer mode only, also show how the comment would be routed before asking the action — append:
Would post as: inline comment at <file>:<line>
— or —
Would post in: general review body
Preamble: (<the routing-explanation note>)
This makes the routing transparent and gives the user a final sanity check before they answer the action menu.
5. Walkthrough — developer mode
Per-finding AskUserQuestion (4 options):
| Action | Behavior |
|---|---|
| Fix it for me | Read the finding's suggestion field. If concrete (code snippet, line ranges), draft the edit via Edit tool, show the diff to the user, mark the finding addressed. If vague, fall back to "Chat about it" inline — present 2–3 possible resolutions, let the user pick. |
| Fix it later | Append the finding to $RUN_DIR/todo.md (create if missing). Format: ## <severity>: <title>\n\nFile: :\n\n<body>\n\nSuggestion: <suggestion>\n\n---. |
| Skip | Mark deferred-without-todo. Don't fix, don't track. |
| Chat about it | Discuss with the user. They can change their mind to any of the other 3 actions afterward. |
Do not commit or stage. Leave the working tree dirty for the user.
After the loop, summarize in chat: "Applied N fixes across M files. K findings sent to $RUN_DIR/todo.md. L skipped."
6. Walkthrough — reviewer mode
No upfront mode question. Always batched — posts go out as one PR review at the end, after a per-comment verification pass (see step 7). The earlier draft of this skill asked "batched or drive-by?" up front; that question is bad UX because the user can't meaningfully decide before they see what's going to be posted. Always batch.
Comment routing — automatic, not a user choice. Each finding the user chooses to post is routed to exactly one place; the finding's shape decides where:
- File-anchored findings (
fileandlineare non-null ANDlinefalls inside a diff hunk on the PR's HEAD commit) → inline comment at that file:line. - Everything else (cross-cutting concerns, findings about files not in the diff, findings without a specific line anchor, PR-description issues, findings whose anchor line falls outside the diff hunks) → general review body.
This routing happens silently. The user is not asked "inline or body?" per finding — the data answers it. We hit Line could not be resolved 422s during testing precisely because anchors outside hunks were attempted; the routing rule prevents that by construction.
Per-finding AskUserQuestion (3 options):
| Action | Behavior |
|---|---|
| Post | Route per the rule above. Inline → accumulate {path, line, body} in $RUN_DIR/draft-review.json's comments array. Body → append to the running general-body buffer with a ### <finding title> heading. |
| Skip | Mark skipped, don't include. |
| Chat about it | Workshop the comment text with the user. Afterward they can pick Post or Skip. They cannot override the routing — that follows the rule. |
When the routing pushes a finding into the body, the skill prepends a one-line context note so it's clear why it's in the body and not inline. Examples:
(File \xrpl/wallet/wallet.go` not in this PR's diff but affected by the contract change.)`(Concerns the PR description / changelog wording, not a specific file.)(Anchor line falls outside the diff hunks for \`; surfaced here.)`
Comment text conventions (what gets posted vs what's shown in chat)
The chat display (the merged-review-style template above) shows internal metadata so the user can make decisions: Reviewer agreement: 3/4, Sources: /review, /code-review, the finding id, etc.
None of that metadata is included in the comment text posted to GitHub. The posted comment is just the substance:
- Optional severity prefix:
Nit:,Concern:(omit for Blockers — the REQUEST_CHANGES event carries that signal). - The finding
bodyfrom the JSON. - The
suggestionblock, if present.
Things that must NEVER appear in a posted comment:
- "(2/4 reviewers)" / "Reviewer agreement: N/M" — internal weighting, noise to the author.
- "Multi-review surfaced this" / mention of the skill / "(4 reviewers · /review × 2 + /code-review × 2)" — credit boilerplate. The author doesn't care which tool produced the review.
- Finding IDs or sequence numbers — those are walkthrough nav, not part of the comment.
- The finding
titleif it would be redundant with the first line ofbody. If you do prepend the title for readability, drop any "(2/4 reviewers)"-style suffix from it.
The walkthrough/verification chat shows the metadata. The PR sees only the substance.
7. Submit / wrap up
Reviewer mode — single preview, then submit:
The walkthrough already approved each comment individually. Don't re-walk them one by one — that's redundant work. Instead, show the full payload once as a single preview, and ask one question: ship it, edit a specific item, or cancel.
Render the full preview — concatenate every staged comment with its routing context. No metadata. Concretely:
════════════════════════════════════════════ Ready to post to PR #<N>: <M> inline + <K> body comments Event: COMMENT (default — change below) ════════════════════════════════════════════ ── Inline comments ── [1] <file>:<line> [Nit] <comment text> [2] <file>:<line> [Concern] <comment text> ── General review body ── <one-sentence verdict> [3] (File `xrpl/wallet/wallet.go` not in this PR's diff …) <comment text> ════════════════════════════════════════════IDs
[N]are local to the preview, used by the action menu — not posted.Single confirmation —
AskUserQuestionwith 4 options:Action Behavior Submit as is (default) Continue to step 3 (event-type prompt). Edit comment N Ask which [N]and what to change. Apply, re-render the preview, ask again.Drop comment N Ask which [N]. Remove from payload, re-render, ask again.Cancel review Abort. Save $RUN_DIR/draft-review.jsonfor inspection. Nothing posted.The Edit/Drop branches loop back to the preview after applying — so the user can make a few targeted changes and then
Submit. They never re-walk every comment.Event type: after the preview is approved, ask:
What event type for the review?
COMMENT(default) — neutral feedbackREQUEST_CHANGES— required changes before mergeAPPROVE— sign off
Recommend
REQUEST_CHANGESif any verified comment came from a Blocker-severity finding. Don't force.Compose the final payload:
{ "commit_id": "<HEAD of PR>", "event": "<chosen event>", "body": "<accumulated general body>", "comments": [{ "path": "...", "line": ..., "side": "RIGHT", "body": "..." }, ...] }General body format — keep it short. The body is what the author reads first. It should be:
- At most one short sentence of verdict context (e.g., "Approve modulo the inline nits."). Skip entirely if the inline comments speak for themselves.
- Then the body-routed findings, each with its routing-preamble line and the comment text.
Things that must NOT appear in the body:
- A "Multi-review summary" header or any mention of the skill / number of reviewers / which agents produced it.
- A restatement of what the PR does. The author already knows. The body summarizes findings, not the change.
- A "Verdict: Approve with nits" boilerplate line as a separate header — that signal is carried by the
eventfield, not the body text. - Findings without a real reason to be there. If you have zero body-routed findings, the body should be one short verdict sentence, or empty.
Example of a good body (zero body-routed findings):
Approve modulo the inline nits.
Example of a good body (one body-routed finding):
Approve modulo the inline nits.
(File
xrpl/wallet/wallet.gonot in this PR's diff but affected by the contract change.) Orphanmaps.Copyatwallet.go:144-146is now dead weight — same workaround as the three you cleaned up incounterparty_signer.go. Consider removing for consistency.Bad body (the one the user flagged): multi-paragraph PR summary + verdict header + reviewer-process credit + then findings. Cut all of that.
Pre-clean any pending review by the current user on this PR — delete it via
gh api .../pulls/$PR/reviews/<id> -X DELETE. (User can only have one pending review per pull request422 otherwise.)Submit:
gh api repos/$GH_REPO/pulls/$PR/reviews -X POST --input <payload>. Print the review URL.
Developer mode:
- See step 5. No verification pass — file edits are visible in the working tree as they happen, so verification is just "look at the diff I just showed you" per finding.
Notes
- REPO is from git, not gh. Always parse
git remote get-url origin. The user may be on a fork;gh's notion of "default repo" can diverge from the actual upstream. - The merger must verify before propagating. The bash script's merger prompt instructs the merger to read actual source files before propagating any correctness claim. We hit a false positive in testing (a
Concern-level "nested mutation risk" that didn't reproduce); the verification step caught it. Don't relax this. - Never auto-commit or auto-amend. Always leave staging to the user. The skill is allowed to edit files (in developer mode "Fix it for me") and to call
gh api(in reviewer mode), but never to mutate git refs. - Cost: 4× Opus is real money. Mention it once at the start of a run if no PR number was passed (heuristic: explicit PR # = the user knows what they're doing). Don't nag.
Resuming or re-walking past runs
The skill never overwrites or deletes previous run directories. To walk an existing run again, the user says so in chat — e.g., "walk through reviews/pr-272-xrpl-fix-uint64-ambiguity-20260512-094530". Resume by jumping directly to step 4 with that $RUN_DIR. No flag, no auto-detect — just natural language.