Review-Requested PRs Skill
Goal: find the PRs awaiting my review, let me pick repos then PRs, and for each chosen PR leave a GitHub review in PENDING state (a draft I review/finish/submit myself on the UI). Never submit a review on your own initiative — leave it PENDING. The only exception is when I explicitly ask you to publish / submit / send it: then follow Step 6 (ask me the review type first).
gh is authenticated. Resolve my login once: gh api user --jq .login (call it ME).
Targeted-PR mode — when I pass a specific PR
If the invocation args contain a reference to one specific PR, skip discovery and selection entirely (Steps 1–3) and go straight to Step 4 on that single PR. Accepted forms:
- a full URL
https://github.com/<OWNER>/<REPO>/pull/<N> - a short ref
<OWNER>/<REPO>#<N> - a bare
#<N>or<N>— only valid when run from inside a repo dir; resolve<OWNER>/<REPO>withgh repo view --json nameWithOwner --jq .nameWithOwner.
Parse out REPO (as <OWNER>/<REPO>) and N, state which PR you're reviewing up front, then run
Step 4 (4a → 4e) for that one PR and report via Step 5. The dry-run flag still composes — args can
contain both a PR link and dry-run. A single targeted PR doesn't need its own worktree subagent;
review it inline (still trace calls end to end per 4a). Everything else — comment style, pending
mechanics, the "never submit" rule — is unchanged.
If no PR reference is present in the args, fall through to the discovery flow below (Step 1 onward).
Mode — live vs dry-run
Default is live: post pending reviews to GitHub. If the invocation args contain dry-run (or
--dry-run), run in dry-run instead: do everything read-only (discovery, context, analysis) but
make no writes to GitHub at all — no POST/PUT/DELETE, no gh pr review. Output here in the
chat exactly what would have been posted. Steps 1–4c are identical in both modes; only the delivery
(4d) and the report (5) differ. State the active mode up front so I know which one is running.
Step 1 — Discover the PRs awaiting my review
gh search prs --review-requested=@me --state=open --json number,title,repository,url,isDraft,updatedAt --limit 100
review-requested:@mealready gives the right set: GitHub removes me from the requested reviewers once I submit a review, so PRs where I've already left feedback and haven't been re-requested are naturally excluded. The PRs returned are the "awaiting my review" ones.- Drop drafts (
isDraft == true). - If the result is empty, tell me and stop.
Step 2 — Let me pick the repos (default = all)
Group the PRs by repository. Use AskUserQuestion (multiSelect) to let me choose the repos to
review. Make clear that all repos are in scope by default — I'm only deselecting ones to skip.
- If ≤4 repos: one multiSelect question, one option per repo (label = repo, description = PR count).
- If >4 repos (AskUserQuestion caps at 4 options): show the repos as a short numbered list and ask me to reply with the repos to skip (default: keep all).
- If there's a single repo, skip this step.
Step 3 — Show a recap table, then let me pick the PRs (default = all)
For the selected repos, print a markdown recap table: Repo | # | Titre | Mis à jour | URL.
Then let me narrow the PR list:
- If ≤4 PRs: AskUserQuestion multiSelect, one option per PR (label =
repo#number, description = title). - If >4 PRs: keep the table and ask me to reply with the PR numbers to skip (default: review all).
The PRs I keep after this step are the final work list.
Step 4 — Review each chosen PR (pending, never submitted)
Run the PRs concurrently when there are several: one subagent per PR. Reading the branch through a
named ref (4a) needs no checkout, so PRs don't collide — never use isolation: "worktree" (it
creates the worktree inside the repo, which breaks Vitest and Vite). Each subagent owns one PR end to
end and reports back. Give every subagent the rules below verbatim.
4a. Build context — and read what's already been handled
gh pr view <N> --repo <REPO> --json title,body,baseRefName,headRefName,files,additions,deletionsgh pr diff <N> --repo <REPO>and head sha:... --json commits --jq '.commits[-1].oid'- When I am the PR's author (
gh pr view <N> --json author), the review is a self-review: same rules, still PENDING (GitHub refusesREQUEST_CHANGES/APPROVEon one's own PR anyway). - Read prior feedback so I don't repeat myself: existing reviews and their comments
(
gh api repos/<REPO>/pulls/<N>/reviewsand.../reviews/<id>/comments,gh api repos/<REPO>/pulls/<N>/comments). Skip anything already raised and already addressed by later commits; only surface what's still open or new. - Reuse the brief when it exists: if the brief JSON is there (its path, without
.json, is printed bypython3 ~/.claude/skills/pr-brief/blocks.py report <REPO> <N> brief), read it first — its steps, impact and facts are already verified context. Itshead_shamust match the PR's current head; otherwise treat it as stale and rebuild the context from the branch. - Get the full branch for real context (not just the diff), without any checkout: apply the branch
mechanics of
pr-brief, step 2 (~/.claude/skills/pr-brief/SKILL.md) — local clone looked up under~/workspaces,git fetch origin refs/pull/<N>/head:refs/pr-brief/<N>(named ref, neverFETCH_HEAD), read withgit show refs/pr-brief/<N>:<path>andgit grep. Nevergh pr checkoutorgit checkoutin a local clone; when a tool genuinely needs files on disk, use a sibling worktree (git worktree add --detach ../<repo>.worktrees/pr-review-<N> refs/pr-brief/<N>) — that sibling worktree is the only kind allowed. A ref already at the PR's head (left bypr-brieforpr-flow) is reused as is; remember which refs this run fetched, for 4e. Read the changed files and the code they call (scopes, authorization, serializers, migrations; on agorize-front, store consumers, component parents, shared mocks, routes, the companion agorize-core branch) — trace the calls end to end.
4b. Review it as if a junior wrote it
Analyse the PR as if a junior wrote it. Do a real, thorough review and also keep an eye on the choices made: simplicity, the "be clear, not clever" rule, security, etc. The list is a reminder of things not to overlook, not the scope of the review.
4c. Comment style
French, constructive/mentoring, always explain the why. One thread per problem; order them functional bugs first, then contract/data, then tests and conventions. A PR rarely deserves more than ~10 inline comments — past that, group the minor convention points into one comment or into the body. Actionable only — a decision to make, a test to add, a change to do, a point to confirm. No purely positive comments ("bon réflexe", "bien joué", "rien à changer") — these are request-changes reviews. Be concise; quality over quantity.
Every body starts with 🤖 — the review body and each inline comment, no exception. The
easy mistake is prefixing only the review body and forgetting the inline ones; they are separate
bodies and each needs its own marker. Write the prefix as you compose each body, not as a pass
afterwards.
4d. Deliver the review
Dry-run mode — write nothing to GitHub. Don't call any write endpoint (POST/PUT/DELETE) and
don't run gh pr review. Build the exact review you would have posted (head sha, body, and every
inline comment with path, line, side, body) and return it to the orchestrator so it gets
rendered in the chat (see Step 5). Resolving line numbers still matters — they're part of the output.
Live mode — post the review as PENDING (strict mechanics):
- First check no pending review of mine already exists (one draft per user/PR):
gh api repos/<REPO>/pulls/<N>/reviews --jq '.[] | select(.user.login=="<ME>") | "\(.id) \(.state)"'. If a PENDING one exists, report it and don't create a second. - Write the payload in the session scratchpad directory (the sandbox isolates
/tmp, sogh --input /tmp/...fails; never write it into the repo's working tree), e.g.<scratchpad>/review-<N>.json:{ "commit_id": "<HEAD_SHA>", "body": "🤖 <summary + cross-cutting remarks not tied to a line>", "comments": [ {"path": "path/file.rb", "line": <line in the NEW file version>, "side": "RIGHT", "body": "🤖 ..."} ] } - Before posting, grep the payload and confirm every
body(review + each comment) starts with🤖. Fixing it after the fact is expensive:PATCH /pulls/comments/<id>returns 404 on pending review comments (they aren't addressable until submitted), so the only remedy isDELETE /pulls/<N>/reviews/<review_id>then re-POST the whole payload. - An inline comment can only anchor on a line inside a hunk of the PR diff (added or context
line). A finding on a file outside the diff, or on a line no hunk shows, goes into the review
bodywith itspath:line— never as an inline comment (that is the usual 422). line= line number in the file (new version), present in a diff hunk. Useside: "RIGHT"only (avoid deleted lines). For new files, it's still the file line number, not the diff position.- Post in a single request:
gh api --method POST repos/<REPO>/pulls/<N>/reviews --input <scratchpad>/review-<N>.json --jq '"\(.id) \(.state)"' - FORBIDDEN: never include an
"event"field, never rungh pr review. The absence ofeventis exactly what keeps the review a draft; either one would submit it immediately. - On
422 "line could not be resolved": fix the line number (or move that point intobody) and re-post the whole payload. Iterate untilstate=PENDING. Delete the payload file afterwards.
4e. Cleanup
After the subagents finish, delete the refs/pr-brief/<N> refs this run fetched (never a ref
reused from pr-brief/pr-flow, nor one another pass running alongside still reads), remove any sibling
worktree (git worktree remove --force ..., git worktree prune), and confirm the local clone's
working tree and current branch are exactly as they were before the run.
Step 5 — Report back
Live mode — a recap table: Repo#PR | review id | PENDING | nb commentaires | points saillants.
For each review, verify state=PENDING and submitted_at=null. Remind me that a pending review is
visible only to me (with a "Pending" badge) until I click Submit review on the UI, and that
the GET /pulls/<N>/comments endpoint does not list pending comments (use
.../reviews/<id>/comments). On a pending review, those comments come back with line and side
set to null and only position filled: that is normal, not a broken anchor.
Dry-run mode — render here what would have been posted, nothing sent to GitHub. For each PR,
print a section: a Repo#PR — title header, the review body, then each inline comment as
path:line — comment (a list or small table). End with a one-line reminder that this was a dry-run
and that re-running without dry-run is what would actually create the pending reviews.
Step 6 — Publish a review, only when I explicitly ask
Everything above leaves reviews PENDING — never submit on your own initiative. When, and only when, I explicitly ask you to publish / submit / send the review, do this:
- Ask me the review type first — always, before submitting anything. A submitted review is
immutable: its event (
COMMENT/REQUEST_CHANGES/APPROVE) cannot be changed afterward, so never guess or default silently. Use AskUserQuestion (header e.g.Type de review) with:- Request changes (Recommended) — the comments are actionable/blocking (this skill's default posture).
- Comment — neutral, non-blocking feedback.
- Approve — good to merge.
- Submit my PENDING review with the chosen event — this keeps the body and inline comments already
attached, so don't re-post them:
If no PENDING review of mine exists (e.g. it was already submitted once), a submitted review can't be converted — create a new review carrying thegh api repos/<REPO>/pulls/<N>/reviews --jq '.[] | select(.user.login=="<ME>" and .state=="PENDING") | .id' gh api --method POST repos/<REPO>/pulls/<N>/reviews/<review_id>/events \ -f event=<COMMENT|REQUEST_CHANGES|APPROVE> --jq '"\(.id) \(.state) \(.submitted_at)"'eventfield directly. Keep its body short and point back to the existing inline comments instead of duplicating them. - Confirm the resulting
stateback to me (CHANGES_REQUESTED/COMMENTED/APPROVED).