PR Review Loop Purpose Address all open PR review comments one at a time using an opinionated, resumable workflow. Works with comments from any reviewer (human or bot). Typical invocations Users trigger this skill with prompts like: "Address all open review comments on this PR" "Work through the code review feedback on PR #42" "Fix the review comments left by @alice on this pull request" "Use pr-review-loop on PR #123" Prerequisites gh CLI (preferred). If unavailable, fall back to any tool available to interact with GitHub. The PR branch must be checked out locally. Process Step 1 — Pre-flight Inspect the project for safeguard conventions by checking these files (if they exist): CLAUDE.md , AGENTS.md Makefile .github/workflows/ README.md Identify all required safeguards (tests, compilation, linting, formatting, etc.). Run all of them. If any fail, stop immediately and report — do not proceed on a broken baseline. Step 2 — Get Current PR Number and Basic Info
Get current PR details
gh pr status
View PR with all comments
gh pr view Step 3 — Collect Unresolved PR Comments (Source of Truth: reviewThreads)
Fetch review threads and keep only unresolved ones.
gh api graphql
-f
query
=
'
query($owner:String!, $name:String!, $number:Int!) {
repository(owner:$owner, name:$name) {
pullRequest(number:$number) {
reviewThreads(first:100) {
nodes {
id
isResolved
path
line
comments(first:20) {
nodes {
id
databaseId
author { login }
body
createdAt
replyTo { databaseId }
}
}
}
}
}
}
}'
-f
owner
=
'{owner}'
-f
name
=
'{repo}'
-F
number
=
{
pr_number
}
\
|
jq
'.data.repository.pullRequest.reviewThreads.nodes | map(select(.isResolved == false))'
Use unresolved
reviewThreads
as the canonical list for processing. Do not drive the workflow from
/pulls/{pr}/comments
alone.
Step 4 — Triage
Read
the triage guide
for the specific classification framework and examples. If the guide is not available, use the MUST_FIX / SHOULD_FIX / PARK / OUT_OF_SCOPE / NEEDS_CLARIFICATION classification with your own judgment (see definitions below).
Classify every unresolved comment as: MUST_FIX, SHOULD_FIX, PARK, OUT_OF_SCOPE, or NEEDS_CLARIFICATION.
Triage all comments before acting on any.
If a comment is non-actionable/no-op (e.g. acknowledgment, praise, emoji-only), classify as OUT_OF_SCOPE and reply with a short acknowledgment before resolving.
If a comment's intent is genuinely ambiguous — multiple interpretations exist and each would lead to a meaningfully different change — classify as NEEDS_CLARIFICATION rather than guessing.
If Perplexity or other research tools are available and a comment requires external knowledge to classify (e.g., library idioms, language conventions), use them to inform your decision.
Step 5 — Process ONE comment at a time
Process in order: all MUST_FIX first, then SHOULD_FIX.
Skip PARK and OUT_OF_SCOPE for now (they are handled in the summary).
NEEDS_CLARIFICATION comments:
post one focused question as a reply to the thread (see format below), do
not
resolve the thread, and move on to the next comment. Do not implement anything.
Cascading comments:
When multiple comments form a cascade (e.g., changing a trait signature requires updating all impls, callers, and tests), group them into a single commit referencing all comment IDs. Implement the full cascade atomically — applying any single comment without the others would leave the code in an inconsistent state.
For each comment (or group of cascading comments):
5a. Assess complexity
Is this trivial (e.g., rename a function, fix a typo, adjust formatting)?
Yes → fix directly, no plan needed
No → create a plan file at
.pr-review/plan-
/tmp/pr-review-reply- { comment_id } .md << 'EOF' Thanks for the feedback! Before I make a change, I want to make sure I understand what you're after:
EOF jq -n --rawfile body /tmp/pr-review-reply- { comment_id } .md '{body:$body}' /tmp/pr-review-reply- { comment_id } .json gh api repos/ { owner } / { repo } /pulls/ { pull_number } /comments/ { comment_id } /replies \ --input /tmp/pr-review-reply- { comment_id } .json Ask exactly one question. Do not list alternatives unless directly needed to frame the question. Leave the thread unresolved so the reviewer's answer re-surfaces it. 5d. Run safeguards again Run all safeguards. They must all pass. If they fail, fix the regression before moving on — do not skip this step. 5e. Commit and push Each comment gets its own focused commit. Reference the comment author in the message body. git add < changed files
git commit -m "
Addresses PR comment from @ ." git push Example commit flow across multiple comments:
Comment 1: Add missing documentation
git commit -m "docs: add module-level documentation for MetricsRecorder Addresses PR comment from @reviewer about missing module docs." git push
Comment 2: Use Duration instead of i64
git commit -m "refactor: use Duration type for timing parameters Addresses PR comment from @reviewer - improves type safety." git push 5f. Reply to the PR comment Post a reply on the PR comment explaining: What was done (for fixes: reference the commit) Why it was parked (for deferred items) Why it was rejected (for out-of-scope items) Preferred (gh CLI):
Avoid inline backticks/shell interpolation issues by writing body to a file.
cat
/tmp/pr-review-reply- { comment_id } .md << 'EOF'
EOF jq -n --rawfile body /tmp/pr-review-reply- { comment_id } .md '{body:$body}' /tmp/pr-review-reply- { comment_id } .json
POST the reply and capture the response — do NOT run this twice
gh api repos/ { owner } / { repo } /pulls/ { pull_number } /comments/ { comment_id } /replies \ --input /tmp/pr-review-reply- { comment_id } .json
/tmp/pr-review-reply- { comment_id } -response.json 5f.1 Verify reply body Immediately verify what was posted using a GET with the ID from the POST response (never re-run the POST to verify — that creates a duplicate): NEW_REPLY_ID = $( jq '.id' /tmp/pr-review-reply- { comment_id } -response.json ) gh api repos/ { owner } / { repo } /pulls/comments/ $NEW_REPLY_ID | jq '{id, body, html_url}' 5g. Resolve the comment Mark the comment as resolved on GitHub. First, find the thread ID for the comment: gh api graphql -f query = ' query { repository(owner: "{owner}", name: "{repo}") { pullRequest(number: {pr_number}) { reviewThreads(first: 50) { nodes { id isResolved comments(first: 1) { nodes { body } } } } } } }' Then resolve the thread: gh api graphql -f query = ' mutation { resolveReviewThread(input: {threadId: "{thread_id}"}) { thread { id isResolved } } }' Verify resolution: gh api graphql -f query = ' query { node(id: "{thread_id}") { ... on PullRequestReviewThread { id isResolved } } }' 5h. Delete plan file If a plan file was created, delete it: rm .pr-review/plan- < comment-id
.md Step 6 — Stop condition Stop when no MUST_FIX or SHOULD_FIX comments remain. If you prefer to batch-resolve all threads at once rather than one by one, you can do so here: gh api graphql -f query = ' query { repository(owner: "{owner}", name: "{repo}") { pullRequest(number: {pr_number}) { reviewThreads(first: 50) { nodes { id isResolved comments(first: 1) { nodes { body } } } } } } }' | jq -r '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false) | .id' \ | while read thread_id ; do gh api graphql -f query = "mutation { resolveReviewThread(input: {threadId: \" $thread_id \" }) { thread { id } } }" done Step 7 — Summary Post a final comment on the PR summarising:
PR Review Loop — Summary
Fixed
- [commit abc1234] Renamed
footobar(comment by @alice) - ...
Parked
- Refactor of X module deferred — tracked in #
(comment by @bob) - ...
Rejected
- Suggestion to use Y rejected: project convention is Z (comment by @carol)
- ...
Awaiting Clarification
- Asked @alice: "Did you mean to rename this everywhere, or just at the call site?" — thread left open
- ...
Omit any section that has no entries.
Use a body file to avoid shell interpolation:
cat
/tmp/pr-review-summary.md << 'EOF'