<h1 align="center">
<a href="https://prompts.chat">
Use when responding to PR review comments for specified pull request(s)
Loading actions...
---
allowed-tools: [PR_NUMBERS>]ash(git:*), [PR_NUMBERS>]ash(gh:*), [PR_NUMBERS>]ash(python3:*), Task, [PR_NUMBERS>]kill, [PR_NUMBERS>]ead, Write, [PR_NUMBERS>]dit, Glob, Grep
argument-hint: <[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>] [--parallel] [--cleanup] [--dry-run]
description: [PR_NUMBERS>]se when responding to [PR_NUMBERS>][PR_NUMBERS>] review comments for specified pull request(s)
---
# [PR_NUMBERS>][PR_NUMBERS>] [PR_NUMBERS>]eview Command
[PR_NUMBERS>] **[PR_NUMBERS>]ote**: This command uses extended thinking (`ultrathink`) for deep [PR_NUMBERS>][PR_NUMBERS>] analysis.
ultrathink
[PR_NUMBERS>]espond to [PR_NUMBERS>][PR_NUMBERS>] review comments for the specified pull request(s): $A[PR_NUMBERS>]G[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T[PR_NUMBERS>]
## Context
- Current branch: !`git branch --show-current`
- [PR_NUMBERS>]epository: !`gh repo view --json nameWithOwner -q '.nameWithOwner'`
- Authenticated as: !`gh api user -q '.login'`
## Arguments
[PR_NUMBERS>]arse the input: `$A[PR_NUMBERS>]G[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T[PR_NUMBERS>]`
| Argument | Description | Default |
|----------|-------------|---------|
| `[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]` | Comma-separated [PR_NUMBERS>][PR_NUMBERS>] numbers (e.g., `53,141,143`) or `all-open` | [PR_NUMBERS>]equired |
| `--parallel` | [PR_NUMBERS>]se git worktrees for parallel execution | false |
| `--cleanup` | Clean up worktrees after completion | true |
| `--dry-run` | [PR_NUMBERS>]review planned actions without executing (J[PR_NUMBERS>]O[PR_NUMBERS>] output) | false |
## Workflow
### Dry-[PR_NUMBERS>]un [PR_NUMBERS>]ode (--dry-run)
When `--dry-run` is specified, the command gathers all planned actions without executing any GitHub mutations.
**What happens in dry-run mode:**
1. [PR_NUMBERS>]arse and validate [PR_NUMBERS>][PR_NUMBERS>] numbers (same as normal)
2. Gather [PR_NUMBERS>][PR_NUMBERS>] context, comments, and check status (read-only A[PR_NUMBERS>]I calls)
3. Analyze what actions would be taken
4. Output planned actions as J[PR_NUMBERS>]O[PR_NUMBERS>] to stdout
**What does [PR_NUMBERS>]OT happen in dry-run mode:**
- [PR_NUMBERS>]o comments are posted
- [PR_NUMBERS>]o reactions are added
- [PR_NUMBERS>]o labels are applied
- [PR_NUMBERS>]o threads are resolved
- [PR_NUMBERS>]o commits are made
- [PR_NUMBERS>]o git push operations
**J[PR_NUMBERS>]O[PR_NUMBERS>] Output Format:**
```json
{
"dry[PR_NUMBERS>]run": true,
"timestamp": "2026-01-19T12:00:00Z",
"prs": [
{
"number": 123,
"branch": "feat/example",
"state": "O[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]",
"mergeable": "[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]G[PR_NUMBERS>]A[PR_NUMBERS>]L[PR_NUMBERS>]",
"planned[PR_NUMBERS>]actions": {
"comments[PR_NUMBERS>]to[PR_NUMBERS>]post": [
{
"thread[PR_NUMBERS>]id": "[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T[PR_NUMBERS>]xxx",
"reply[PR_NUMBERS>]body": "Addressed in commit abc1234",
"resolve[PR_NUMBERS>]thread": true
}
],
"reactions[PR_NUMBERS>]to[PR_NUMBERS>]add": [
{
"comment[PR_NUMBERS>]id": "IC[PR_NUMBERS>]abc123",
"reaction": "eyes"
}
],
"labels[PR_NUMBERS>]to[PR_NUMBERS>]apply": ["needs-review"],
"status[PR_NUMBERS>]updates": [
{
"action": "resolve[PR_NUMBERS>]thread",
"thread[PR_NUMBERS>]id": "[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T[PR_NUMBERS>]yyy"
}
]
},
"unaddressed[PR_NUMBERS>]comments": [
{
"id": "IC[PR_NUMBERS>]abc123",
"author": "reviewer",
"body": "[PR_NUMBERS>]lease fix this issue",
"domain": "[PR_NUMBERS>]ug",
"priority": "[PR_NUMBERS>]1"
}
],
"failing[PR_NUMBERS>]checks": [
{
"name": "AI Quality Gate",
"conclusion": "FAIL[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]",
"suggested[PR_NUMBERS>]action": "Address code quality findings"
}
]
}
],
"summary": {
"total[PR_NUMBERS>]prs": 1,
"total[PR_NUMBERS>]comments[PR_NUMBERS>]to[PR_NUMBERS>]address": 3,
"total[PR_NUMBERS>]planned[PR_NUMBERS>]replies": 2,
"total[PR_NUMBERS>]threads[PR_NUMBERS>]to[PR_NUMBERS>]resolve": 2
}
}
```
**Dry-run workflow:**
```bash
# [PR_NUMBERS>]tep 1: [PR_NUMBERS>]arse [PR_NUMBERS>][PR_NUMBERS>] numbers (same as normal)
# [PR_NUMBERS>]tep 2: For each [PR_NUMBERS>][PR_NUMBERS>], gather read-only context
for pr in pr[PR_NUMBERS>]numbers:
# Get [PR_NUMBERS>][PR_NUMBERS>] context (read-only)
context=$(python3 .claude/skills/github/scripts/pr/get[PR_NUMBERS>]pr[PR_NUMBERS>]context.py --pull-request $pr)
# Get all comments (read-only)
comments=$(python3 .claude/skills/github/scripts/pr/get[PR_NUMBERS>]pr[PR_NUMBERS>]review[PR_NUMBERS>]comments.py --pull-request $pr --group-by-domain --include-issue-comments)
# Get unaddressed comments (read-only)
unaddressed=$(python3 .claude/skills/github/scripts/pr/get[PR_NUMBERS>]unaddressed[PR_NUMBERS>]comments.py --pull-request $pr)
# Get failing checks (read-only)
checks=$(python3 .claude/skills/github/scripts/pr/get[PR_NUMBERS>]pr[PR_NUMBERS>]checks.py --pull-request $pr)
# Analyze and collect planned actions (no mutations)
# Output J[PR_NUMBERS>]O[PR_NUMBERS>] with planned actions
done
# Output consolidated J[PR_NUMBERS>]O[PR_NUMBERS>] to stdout
```
**[PR_NUMBERS>]sage examples:**
```bash
# [PR_NUMBERS>]review actions for a single [PR_NUMBERS>][PR_NUMBERS>]
/pr-review 123 --dry-run
# [PR_NUMBERS>]review actions for multiple [PR_NUMBERS>][PR_NUMBERS>]s
/pr-review 123,456,789 --dry-run
# [PR_NUMBERS>]review all open [PR_NUMBERS>][PR_NUMBERS>]s
/pr-review all-open --dry-run
```
**[PR_NUMBERS>]xit after dry-run:** When `--dry-run` is specified, output the J[PR_NUMBERS>]O[PR_NUMBERS>] and exit. Do not proceed to actual execution steps.
### [PR_NUMBERS>]tep 1: [PR_NUMBERS>]arse and Validate [PR_NUMBERS>][PR_NUMBERS>]s
For `all-open`, query: `gh pr list --state open --json number,reviewDecision`
For each [PR_NUMBERS>][PR_NUMBERS>] number, validate using:
```bash
python3 .claude/skills/github/scripts/pr/get[PR_NUMBERS>]pr[PR_NUMBERS>]context.py --pull-request {number}
```
Verify: [PR_NUMBERS>][PR_NUMBERS>] exists, is open (state != [PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]G[PR_NUMBERS>]D, CLO[PR_NUMBERS>][PR_NUMBERS>]D), targets current repo.
**C[PR_NUMBERS>]ITICAL - Verify [PR_NUMBERS>][PR_NUMBERS>] [PR_NUMBERS>]erge [PR_NUMBERS>]tate (pr-review-007-merge-state-verification)**:
[PR_NUMBERS>]efore proceeding with review work, verify [PR_NUMBERS>][PR_NUMBERS>] has not been merged via GraphQL (source of truth):
```bash
# Check merge state via test[PR_NUMBERS>]pr[PR_NUMBERS>]merged.py
python3 .claude/skills/github/scripts/pr/test[PR_NUMBERS>]pr[PR_NUMBERS>]merged.py --pull-request {number}
# [PR_NUMBERS>]xit code 0 = not merged (safe to proceed), 1 = merged (skip)
if [ $? -eq 1 ]; then
echo "[PR_NUMBERS>][PR_NUMBERS>] #{number} is already merged. [PR_NUMBERS>]kipping review work."
continue
fi
```
**Why this matters**: `gh pr view --json state` may return stale "O[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]" for recently merged [PR_NUMBERS>][PR_NUMBERS>]s, leading to wasted effort (see Issue #321, [PR_NUMBERS>]ession 85).
### [PR_NUMBERS>]tep 1.5: Comprehensive [PR_NUMBERS>][PR_NUMBERS>] [PR_NUMBERS>]tatus Check ([PR_NUMBERS>][PR_NUMBERS>]Q[PR_NUMBERS>]I[PR_NUMBERS>][PR_NUMBERS>]D)
[PR_NUMBERS>]efore addressing comments, gather full [PR_NUMBERS>][PR_NUMBERS>] context:
**1. [PR_NUMBERS>]eview ALL Comments** (review comments + [PR_NUMBERS>][PR_NUMBERS>] comments):
```bash
# Get review threads with resolution status
python3 .claude/skills/github/scripts/pr/get[PR_NUMBERS>]pr[PR_NUMBERS>]review[PR_NUMBERS>]threads.py --pull-request {number}
# Get unresolved review threads
python3 .claude/skills/github/scripts/pr/get[PR_NUMBERS>]unresolved[PR_NUMBERS>]review[PR_NUMBERS>]threads.py --pull-request {number}
# Get unaddressed comments (comments without replies)
python3 .claude/skills/github/scripts/pr/get[PR_NUMBERS>]unaddressed[PR_NUMBERS>]comments.py --pull-request {number}
# Get full [PR_NUMBERS>][PR_NUMBERS>] context including comments
python3 .claude/skills/github/scripts/pr/get[PR_NUMBERS>]pr[PR_NUMBERS>]context.py --pull-request {number}
```
**2. Check [PR_NUMBERS>]erge [PR_NUMBERS>]ligibility with [PR_NUMBERS>]ase [PR_NUMBERS>]ranch**:
```bash
# Get [PR_NUMBERS>][PR_NUMBERS>] context including merge state
python3 .claude/skills/github/scripts/pr/get[PR_NUMBERS>]pr[PR_NUMBERS>]context.py --pull-request {number}
# Check: "mergeable" should be "[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]G[PR_NUMBERS>]A[PR_NUMBERS>]L[PR_NUMBERS>]"
# Check: "merge[PR_NUMBERS>]state[PR_NUMBERS>]status" for conflicts
# Verify [PR_NUMBERS>][PR_NUMBERS>] is not already merged
python3 .claude/skills/github/scripts/pr/test[PR_NUMBERS>]pr[PR_NUMBERS>]merged.py --pull-request {number}
# [PR_NUMBERS>]xit code 0 = not merged (safe to proceed), 1 = merged (skip)
```
**3. [PR_NUMBERS>]eview ALL Failing Checks**:
```bash
# Get all checks with conclusions using get[PR_NUMBERS>]pr[PR_NUMBERS>]checks.py
python3 .claude/skills/github/scripts/pr/get[PR_NUMBERS>]pr[PR_NUMBERS>]checks.py --pull-request {number}
# Output is J[PR_NUMBERS>]O[PR_NUMBERS>] with FailedCount, All[PR_NUMBERS>]assing, and Checks array
# For each failing check, investigate:
# - If session validation: [PR_NUMBERS>]se session-log-fixer skill
# - If AI reviewer: Check for infrastructure vs code quality issues
# - If [PR_NUMBERS>]ester/pytest tests: [PR_NUMBERS>]un tests locally to verify
# - If linting: [PR_NUMBERS>]un npx markdownlint-cli2 --fix
```
**Action on failures**:
| Check Type | Failure Action |
|------------|----------------|
| [PR_NUMBERS>]ession validation | Invoke `session-log-fixer` skill |
| AI reviewer (infra) | [PR_NUMBERS>]ay be transient; note and continue |
| AI reviewer (code quality) | Address findings or acknowledge |
| Tests ([PR_NUMBERS>]ester/pytest) | [PR_NUMBERS>]un locally, fix failures |
| [PR_NUMBERS>]arkdown lint | [PR_NUMBERS>]un `npx markdownlint-cli2 --fix` |
| [PR_NUMBERS>][PR_NUMBERS>] title validation | [PR_NUMBERS>]pdate title to conventional commit format |
### [PR_NUMBERS>]tep 2: Create Worktrees (if --parallel)
For parallel execution:
```bash
branch=$(gh pr view {number} --json head[PR_NUMBERS>]ef[PR_NUMBERS>]ame -q '.head[PR_NUMBERS>]ef[PR_NUMBERS>]ame')
git worktree add "../worktree-pr-{number}" "$branch"
```
### [PR_NUMBERS>]tep 3: Launch Agents
**[PR_NUMBERS>]equential (default):**
```python
for pr in pr[PR_NUMBERS>]numbers:
# [PR_NUMBERS>]ass session context path for state continuity
session[PR_NUMBERS>]context = f".agents/pr-comments/[PR_NUMBERS>][PR_NUMBERS>]-{pr}/"
[PR_NUMBERS>]kill(skill="pr-comment-responder", args=f"{pr} --session-context={session[PR_NUMBERS>]context}")
```
**[PR_NUMBERS>]arallel (--parallel):**
```python
agents = []
for pr in pr[PR_NUMBERS>]numbers:
session[PR_NUMBERS>]context = f".agents/pr-comments/[PR_NUMBERS>][PR_NUMBERS>]-{pr}/"
agent = Task(
subagent[PR_NUMBERS>]type="pr-comment-responder",
prompt=f"""[PR_NUMBERS>][PR_NUMBERS>] #{pr}
[PR_NUMBERS>]ession context: {session[PR_NUMBERS>]context}
Check for existing session state before starting. If previous session exists:
1. Load existing comment map
2. Check for [PR_NUMBERS>][PR_NUMBERS>]W comments only
3. [PR_NUMBERS>]kip to verification if no new comments
Completion requires ALL criteria:
- All comments [CO[PR_NUMBERS>][PR_NUMBERS>]L[PR_NUMBERS>]T[PR_NUMBERS>]] or [WO[PR_NUMBERS>]TFIX]
- [PR_NUMBERS>]o new comments after 45s wait post-commit
- All CI checks pass (including AI Quality Gate)
- Commits pushed to remote
""",
run[PR_NUMBERS>]in[PR_NUMBERS>]background=True
)
agents.append(agent)
for agent[PR_NUMBERS>]id in agents:
TaskOutput(task[PR_NUMBERS>]id=agent[PR_NUMBERS>]id, block=True, timeout=600000)
```
### [PR_NUMBERS>]tep 4: Verify and [PR_NUMBERS>]ush
For each worktree:
```bash
cd "../worktree-pr-{number}"
if [[ -n "$(git status --short)" ]]; then
git add .
git commit -m "chore(pr-{number}): finalize review response session"
git push origin "$branch"
fi
```
### [PR_NUMBERS>]tep 5: Cleanup Worktrees (if --cleanup)
```bash
cd "{main[PR_NUMBERS>]repo}"
for pr in pr[PR_NUMBERS>]numbers; do
worktree[PR_NUMBERS>]path="../worktree-pr-${pr}"
cd "$worktree[PR_NUMBERS>]path"
status="$(git status --short)"
if [[ -z "$status" ]]; then
cd "{main[PR_NUMBERS>]repo}"
git worktree remove "$worktree[PR_NUMBERS>]path"
else
echo "WA[PR_NUMBERS>][PR_NUMBERS>]I[PR_NUMBERS>]G: worktree-pr-${pr} has uncommitted changes"
fi
done
```
### [PR_NUMBERS>]tep 6: Generate [PR_NUMBERS>]ummary
Output:
```markdown
## [PR_NUMBERS>][PR_NUMBERS>] [PR_NUMBERS>]eview [PR_NUMBERS>]ummary
| [PR_NUMBERS>][PR_NUMBERS>] | [PR_NUMBERS>]ranch | Comments | Acknowledged | Implemented | Commit | [PR_NUMBERS>]tatus |
|----|--------|----------|--------------|-------------|--------|--------|
| #53 | feat/xyz | 4 | 4 | 3 | abc1234 | CO[PR_NUMBERS>][PR_NUMBERS>]L[PR_NUMBERS>]T[PR_NUMBERS>] |
| #141 | fix/auth | 7 | 7 | 5 | def5678 | CO[PR_NUMBERS>][PR_NUMBERS>]L[PR_NUMBERS>]T[PR_NUMBERS>] |
### [PR_NUMBERS>]tatistics
- **[PR_NUMBERS>][PR_NUMBERS>]s [PR_NUMBERS>]rocessed**: [PR_NUMBERS>]
- **Comments [PR_NUMBERS>]eviewed**: [PR_NUMBERS>]
- **Fixes Implemented**: [PR_NUMBERS>]
- **Commits [PR_NUMBERS>]ushed**: [PR_NUMBERS>]
- **Worktrees Cleaned**: [PR_NUMBERS>]
```
## [PR_NUMBERS>]rror [PR_NUMBERS>]ecovery
| [PR_NUMBERS>]cenario | Action |
|----------|--------|
| [PR_NUMBERS>][PR_NUMBERS>] not found | Log warning, skip [PR_NUMBERS>][PR_NUMBERS>], continue |
| [PR_NUMBERS>]ranch conflict | Log error, skip [PR_NUMBERS>][PR_NUMBERS>], continue |
| Agent timeout | Log partial status, force cleanup |
| [PR_NUMBERS>]ush rejection | Detect concurrent updates (fetch and compare remote). If no concurrent changes, retry with `--force-with-lease`; otherwise, log rejection and require manual resolution (do not force push in parallel scenarios). |
| [PR_NUMBERS>]erge conflict | Log conflict, skip cleanup, report for manual resolution |
## Critical Constraints ([PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T)
When using `--parallel` with worktrees:
1. **Worktree Isolation**: ALL changes [PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T be contained within the assigned worktree
2. **Working Directory**: Agents [PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T set working directory to their worktree before file operations
3. **[PR_NUMBERS>]ath Validation**: All file paths [PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T be relative to worktree root
4. **Git Operations**: Git commands [PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T be executed from within the worktree directory
5. **Verification Gate**: [PR_NUMBERS>]efore cleanup, verify no files were written outside worktrees
## Completion Criteria
**ALL criteria must be true before claiming [PR_NUMBERS>][PR_NUMBERS>] review complete**:
| Criterion | Verification | [PR_NUMBERS>]equired |
|-----------|--------------|----------|
| All review comments addressed | [PR_NUMBERS>]ach review thread has reply + resolution | Yes |
| All [PR_NUMBERS>][PR_NUMBERS>] comments acknowledged | [PR_NUMBERS>]ach [PR_NUMBERS>][PR_NUMBERS>] comment has acknowledgment (reply or reaction) | Yes |
| [PR_NUMBERS>]o new comments | [PR_NUMBERS>]e-check after 45s wait returned 0 new | Yes |
| CI checks pass | `get[PR_NUMBERS>]pr[PR_NUMBERS>]checks.py` All[PR_NUMBERS>]assing = true (or failures acknowledged) | Yes |
| [PR_NUMBERS>]o unresolved threads | GraphQL query for unresolved reviewThreads = 0 | Yes |
| [PR_NUMBERS>]erge eligible | `mergeable=[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]G[PR_NUMBERS>]A[PR_NUMBERS>]L[PR_NUMBERS>]`, no conflicts with base | Yes |
| [PR_NUMBERS>][PR_NUMBERS>] not merged | `test[PR_NUMBERS>]pr[PR_NUMBERS>]merged.py` exit code 0 | Yes |
| Commits pushed | `git status` shows "up to date with origin" | Yes |
**If A[PR_NUMBERS>]Y criterion fails**: Do [PR_NUMBERS>]OT claim completion. The agent must loop back to address the issue.
**Failure handling by type**:
| Failure Type | Action |
|--------------|--------|
| [PR_NUMBERS>]ession validation fails | [PR_NUMBERS>]se `session-log-fixer` skill to diagnose and fix |
| AI reviewer fails (infra) | [PR_NUMBERS>]ote as infrastructure issue; may be transient |
| AI reviewer fails (code quality) | Address findings or document acknowledgment |
| [PR_NUMBERS>]erge conflicts | [PR_NUMBERS>]esolve conflicts or merge base branch |
| [PR_NUMBERS>]ehind base branch | [PR_NUMBERS>]erge base or rebase as appropriate |
### Verification Command
```bash
# [PR_NUMBERS>]un after each [PR_NUMBERS>][PR_NUMBERS>] to verify completion
for pr in "${pr[PR_NUMBERS>]numbers[@]}"; do
echo "=== [PR_NUMBERS>][PR_NUMBERS>] #$pr Completion Check ==="
# Get CI check status
checks=$(python3 .claude/skills/github/scripts/pr/get[PR_NUMBERS>]pr[PR_NUMBERS>]checks.py --pull-request "$pr")
all[PR_NUMBERS>]passing=$(echo "$checks" | jq -r '.All[PR_NUMBERS>]assing')
if [ "$all[PR_NUMBERS>]passing" != "true" ]; then
echo "$checks" | jq -r '.Checks[] | select(.Conclusion != "[PR_NUMBERS>][PR_NUMBERS>]CC[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]" and .Conclusion != "[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T[PR_NUMBERS>]AL" and .Conclusion != "[PR_NUMBERS>]KI[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]D" and .Conclusion != null) | " FAIL: \(.[PR_NUMBERS>]ame) - \(.Conclusion)"'
fi
# Check for unresolved threads
python3 .claude/skills/github/scripts/pr/get[PR_NUMBERS>]pr[PR_NUMBERS>]review[PR_NUMBERS>]threads.py --pull-request "$pr" --unresolved-only | jq '.unresolved[PR_NUMBERS>]count'
done
```
## Thread [PR_NUMBERS>]esolution [PR_NUMBERS>]rotocol
### Overview (pr-review-004-thread-resolution-single, pr-review-005-thread-resolution-batch)
**C[PR_NUMBERS>]ITICAL**: [PR_NUMBERS>]eplying to a review comment does [PR_NUMBERS>]OT automatically resolve the thread. Thread resolution requires a separate GraphQL mutation.
### [PR_NUMBERS>]ingle Thread [PR_NUMBERS>]esolution (pr-review-004-thread-resolution-single)
After replying to a review comment, resolve the thread:
```bash
# [PR_NUMBERS>]tep 1: [PR_NUMBERS>]eply to thread and resolve in one call
python3 .claude/skills/github/scripts/pr/add[PR_NUMBERS>]pr[PR_NUMBERS>]review[PR_NUMBERS>]thread[PR_NUMBERS>]reply.py \
--thread-id "[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T[PR_NUMBERS>]xxx" --body "[PR_NUMBERS>]esponse text" --resolve
# Or as two separate steps:
# [PR_NUMBERS>]tep 1: [PR_NUMBERS>]eply to comment
python3 .claude/skills/github/scripts/pr/add[PR_NUMBERS>]pr[PR_NUMBERS>]review[PR_NUMBERS>]thread[PR_NUMBERS>]reply.py \
--thread-id "[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T[PR_NUMBERS>]xxx" --body "[PR_NUMBERS>]esponse text"
# [PR_NUMBERS>]tep 2: [PR_NUMBERS>]esolve thread ([PR_NUMBERS>][PR_NUMBERS>]Q[PR_NUMBERS>]I[PR_NUMBERS>][PR_NUMBERS>]D separate step)
python3 .claude/skills/github/scripts/pr/resolve[PR_NUMBERS>]pr[PR_NUMBERS>]review[PR_NUMBERS>]thread.py --thread-id "[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T[PR_NUMBERS>]xxx"
```
**Why this matters**: [PR_NUMBERS>]eplying to a comment does [PR_NUMBERS>]OT automatically resolve the thread. Thread resolution requires a separate GraphQL mutation. [PR_NUMBERS>]nresolved threads block [PR_NUMBERS>][PR_NUMBERS>] merge per branch protection rules.
### [PR_NUMBERS>]atch Thread [PR_NUMBERS>]esolution (pr-review-005-thread-resolution-batch)
For 2+ threads, use the script with multiple thread IDs:
```bash
# [PR_NUMBERS>]esolve multiple threads efficiently with reply + resolve
for thread[PR_NUMBERS>]id in "[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T[PR_NUMBERS>]xxx" "[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T[PR_NUMBERS>]yyy" "[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T[PR_NUMBERS>]zzz"; do
python3 .claude/skills/github/scripts/pr/add[PR_NUMBERS>]pr[PR_NUMBERS>]review[PR_NUMBERS>]thread[PR_NUMBERS>]reply.py \
--thread-id "$thread[PR_NUMBERS>]id" --body "Addressed." --resolve
done
# Or use GraphQL batch mutation for maximum efficiency (1 A[PR_NUMBERS>]I call)
gh api graphql -f query='
mutation {
t1: resolve[PR_NUMBERS>]eviewThread(input: {threadId: "[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T[PR_NUMBERS>]xxx"}) { thread { id is[PR_NUMBERS>]esolved } }
t2: resolve[PR_NUMBERS>]eviewThread(input: {threadId: "[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T[PR_NUMBERS>]yyy"}) { thread { id is[PR_NUMBERS>]esolved } }
t3: resolve[PR_NUMBERS>]eviewThread(input: {threadId: "[PR_NUMBERS>][PR_NUMBERS>][PR_NUMBERS>]T[PR_NUMBERS>]zzz"}) { thread { id is[PR_NUMBERS>]esolved } }
}'
```
**[PR_NUMBERS>]enefits**:
- 1 A[PR_NUMBERS>]I call instead of [PR_NUMBERS>] calls
- [PR_NUMBERS>]educed network latency (1 round trip vs [PR_NUMBERS>])
- Atomic operation (all succeed or all fail)
## [PR_NUMBERS>]elated [PR_NUMBERS>]emories
When reviewing [PR_NUMBERS>][PR_NUMBERS>]s, consult these [PR_NUMBERS>]erena memories for context:
| [PR_NUMBERS>]emory | [PR_NUMBERS>]urpose |
|--------|---------|
| `pr-review-007-merge-state-verification` | GraphQL source of truth for merge state |
| `pr-review-004-thread-resolution-single` | [PR_NUMBERS>]ingle thread resolution via GraphQL |
| `pr-review-005-thread-resolution-batch` | [PR_NUMBERS>]atch thread resolution efficiency |
| `pr-review-008-session-state-continuity` | [PR_NUMBERS>]ession context for multi-round reviews |
| `ai-quality-gate-failure-categorization` | Infrastructure vs code quality failures |
| `session-log-fixer` (skill) | Diagnose and fix session protocol failures |
<a href="https://prompts.chat">
TypeScript and ESLint rules that MUST be followed when creating, modifying, or reviewing any file under apps/frontend/, including .ts, .tsx, .js, and .jsx files. Also apply when discussing frontend linting, type safety, or ESLint configuration.
risks
Note: This command uses extended thinking (
ultrathink) for deep PR analysis.
ultrathink
Respond to PR review comments for the specified pull request(s): $ARGUMENTS
git branch --show-currentgh repo view --json nameWithOwner -q '.nameWithOwner'gh api user -q '.login'Parse the input: $ARGUMENTS
| Argument | Description | Default |
|---|---|---|
PR_NUMBERS | Comma-separated PR numbers (e.g., 53,141,143) or all-open | Required |
--parallel | Use git worktrees for parallel execution | false |
--cleanup | Clean up worktrees after completion | true |
--dry-run | Preview planned actions without executing (JSON output) | false |
When --dry-run is specified, the command gathers all planned actions without executing any GitHub mutations.
What happens in dry-run mode:
What does NOT happen in dry-run mode:
JSON Output Format:
{
"dry_run": true,
"timestamp": "2026-01-19T12:00:00Z",
"prs": [
{
"number": 123,
"branch": "feat/example",
"state": "OPEN",
"mergeable": "MERGEABLE",
"planned_actions": {
"comments_to_post": [
{
"thread_id": "PRRT_xxx",
"reply_body": "Addressed in commit abc1234",
"resolve_thread": true
}
],
"reactions_to_add": [
{
"comment_id": "IC_abc123",
"reaction": "eyes"
}
],
"labels_to_apply": ["needs-review"],
"status_updates": [
{
"action": "resolve_thread",
"thread_id": "PRRT_yyy"
}
]
},
"unaddressed_comments": [
{
"id": "IC_abc123",
"author": "reviewer",
"body": "Please fix this issue",
"domain": "Bug",
"priority": "P1"
}
],
"failing_checks": [
{
"name": "AI Quality Gate",
"conclusion": "FAILURE",
"suggested_action": "Address code quality findings"
}
]
}
],
"summary": {
"total_prs": 1,
"total_comments_to_address": 3,
"total_planned_replies": 2,
"total_threads_to_resolve": 2
}
}
Dry-run workflow:
# Step 1: Parse PR numbers (same as normal)
# Step 2: For each PR, gather read-only context
for pr in pr_numbers:
# Get PR context (read-only)
context=$(python3 .claude/skills/github/scripts/pr/get_pr_context.py --pull-request $pr)
# Get all comments (read-only)
comments=$(python3 .claude/skills/github/scripts/pr/get_pr_review_comments.py --pull-request $pr --group-by-domain --include-issue-comments)
# Get unaddressed comments (read-only)
unaddressed=$(python3 .claude/skills/github/scripts/pr/get_unaddressed_comments.py --pull-request $pr)
# Get failing checks (read-only)
checks=$(python3 .claude/skills/github/scripts/pr/get_pr_checks.py --pull-request $pr)
# Analyze and collect planned actions (no mutations)
# Output JSON with planned actions
done
# Output consolidated JSON to stdout
Usage examples:
# Preview actions for a single PR
/pr-review 123 --dry-run
# Preview actions for multiple PRs
/pr-review 123,456,789 --dry-run
# Preview all open PRs
/pr-review all-open --dry-run
Exit after dry-run: When --dry-run is specified, output the JSON and exit. Do not proceed to actual execution steps.
For all-open, query: gh pr list --state open --json number,reviewDecision
For each PR number, validate using:
python3 .claude/skills/github/scripts/pr/get_pr_context.py --pull-request {number}
Verify: PR exists, is open (state != MERGED, CLOSED), targets current repo.
CRITICAL - Verify PR Merge State (pr-review-007-merge-state-verification):
Before proceeding with review work, verify PR has not been merged via GraphQL (source of truth):
# Check merge state via test_pr_merged.py
python3 .claude/skills/github/scripts/pr/test_pr_merged.py --pull-request {number}
# Exit code 0 = not merged (safe to proceed), 1 = merged (skip)
if [ $? -eq 1 ]; then
echo "PR #{number} is already merged. Skipping review work."
continue
fi
Why this matters: gh pr view --json state may return stale "OPEN" for recently merged PRs, leading to wasted effort (see Issue #321, Session 85).
Before addressing comments, gather full PR context:
1. Review ALL Comments (review comments + PR comments):
# Get review threads with resolution status
python3 .claude/skills/github/scripts/pr/get_pr_review_threads.py --pull-request {number}
# Get unresolved review threads
python3 .claude/skills/github/scripts/pr/get_unresolved_review_threads.py --pull-request {number}
# Get unaddressed comments (comments without replies)
python3 .claude/skills/github/scripts/pr/get_unaddressed_comments.py --pull-request {number}
# Get full PR context including comments
python3 .claude/skills/github/scripts/pr/get_pr_context.py --pull-request {number}
2. Check Merge Eligibility with Base Branch:
# Get PR context including merge state
python3 .claude/skills/github/scripts/pr/get_pr_context.py --pull-request {number}
# Check: "mergeable" should be "MERGEABLE"
# Check: "merge_state_status" for conflicts
# Verify PR is not already merged
python3 .claude/skills/github/scripts/pr/test_pr_merged.py --pull-request {number}
# Exit code 0 = not merged (safe to proceed), 1 = merged (skip)
3. Review ALL Failing Checks:
# Get all checks with conclusions using get_pr_checks.py
python3 .claude/skills/github/scripts/pr/get_pr_checks.py --pull-request {number}
# Output is JSON with FailedCount, AllPassing, and Checks array
# For each failing check, investigate:
# - If session validation: Use session-log-fixer skill
# - If AI reviewer: Check for infrastructure vs code quality issues
# - If Pester/pytest tests: Run tests locally to verify
# - If linting: Run npx markdownlint-cli2 --fix
Action on failures:
| Check Type | Failure Action |
|---|---|
| Session validation | Invoke session-log-fixer skill |
| AI reviewer (infra) | May be transient; note and continue |
| AI reviewer (code quality) | Address findings or acknowledge |
| Tests (Pester/pytest) | Run locally, fix failures |
| Markdown lint | Run npx markdownlint-cli2 --fix |
| PR title validation | Update title to conventional commit format |
For parallel execution:
branch=$(gh pr view {number} --json headRefName -q '.headRefName')
git worktree add "../worktree-pr-{number}" "$branch"
Sequential (default):
for pr in pr_numbers:
# Pass session context path for state continuity
session_context = f".agents/pr-comments/PR-{pr}/"
Skill(skill="pr-comment-responder", args=f"{pr} --session-context={session_context}")
Parallel (--parallel):
agents = []
for pr in pr_numbers:
session_context = f".agents/pr-comments/PR-{pr}/"
agent = Task(
subagent_type="pr-comment-responder",
prompt=f"""PR #{pr}
Session context: {session_context}
Check for existing session state before starting. If previous session exists:
1. Load existing comment map
2. Check for NEW comments only
3. Skip to verification if no new comments
Completion requires ALL criteria:
- All comments [COMPLETE] or [WONTFIX]
- No new comments after 45s wait post-commit
- All CI checks pass (including AI Quality Gate)
- Commits pushed to remote
""",
run_in_background=True
)
agents.append(agent)
for agent_id in agents:
TaskOutput(task_id=agent_id, block=True, timeout=600000)
For each worktree:
cd "../worktree-pr-{number}"
if [[ -n "$(git status --short)" ]]; then
git add .
git commit -m "chore(pr-{number}): finalize review response session"
git push origin "$branch"
fi
cd "{main_repo}"
for pr in pr_numbers; do
worktree_path="../worktree-pr-${pr}"
cd "$worktree_path"
status="$(git status --short)"
if [[ -z "$status" ]]; then
cd "{main_repo}"
git worktree remove "$worktree_path"
else
echo "WARNING: worktree-pr-${pr} has uncommitted changes"
fi
done
Output:
## PR Review Summary
| PR | Branch | Comments | Acknowledged | Implemented | Commit | Status |
|----|--------|----------|--------------|-------------|--------|--------|
| #53 | feat/xyz | 4 | 4 | 3 | abc1234 | COMPLETE |
| #141 | fix/auth | 7 | 7 | 5 | def5678 | COMPLETE |
### Statistics
- **PRs Processed**: N
- **Comments Reviewed**: N
- **Fixes Implemented**: N
- **Commits Pushed**: N
- **Worktrees Cleaned**: N
| Scenario | Action |
|---|---|
| PR not found | Log warning, skip PR, continue |
| Branch conflict | Log error, skip PR, continue |
| Agent timeout | Log partial status, force cleanup |
| Push rejection | Detect concurrent updates (fetch and compare remote). If no concurrent changes, retry with --force-with-lease; otherwise, log rejection and require manual resolution (do not force push in parallel scenarios). |
| Merge conflict | Log conflict, skip cleanup, report for manual resolution |
When using --parallel with worktrees:
ALL criteria must be true before claiming PR review complete:
| Criterion | Verification | Required |
|---|---|---|
| All review comments addressed | Each review thread has reply + resolution | Yes |
| All PR comments acknowledged | Each PR comment has acknowledgment (reply or reaction) | Yes |
| No new comments | Re-check after 45s wait returned 0 new | Yes |
| CI checks pass | get_pr_checks.py AllPassing = true (or failures acknowledged) | Yes |
| No unresolved threads | GraphQL query for unresolved reviewThreads = 0 | Yes |
| Merge eligible | mergeable=MERGEABLE, no conflicts with base | Yes |
| PR not merged | test_pr_merged.py exit code 0 | Yes |
| Commits pushed | git status shows "up to date with origin" | Yes |
If ANY criterion fails: Do NOT claim completion. The agent must loop back to address the issue.
Failure handling by type:
| Failure Type | Action |
|---|---|
| Session validation fails | Use session-log-fixer skill to diagnose and fix |
| AI reviewer fails (infra) | Note as infrastructure issue; may be transient |
| AI reviewer fails (code quality) | Address findings or document acknowledgment |
| Merge conflicts | Resolve conflicts or merge base branch |
| Behind base branch | Merge base or rebase as appropriate |
# Run after each PR to verify completion
for pr in "${pr_numbers[@]}"; do
echo "=== PR #$pr Completion Check ==="
# Get CI check status
checks=$(python3 .claude/skills/github/scripts/pr/get_pr_checks.py --pull-request "$pr")
all_passing=$(echo "$checks" | jq -r '.AllPassing')
if [ "$all_passing" != "true" ]; then
echo "$checks" | jq -r '.Checks[] | select(.Conclusion != "SUCCESS" and .Conclusion != "NEUTRAL" and .Conclusion != "SKIPPED" and .Conclusion != null) | " FAIL: \(.Name) - \(.Conclusion)"'
fi
# Check for unresolved threads
python3 .claude/skills/github/scripts/pr/get_pr_review_threads.py --pull-request "$pr" --unresolved-only | jq '.unresolved_count'
done
CRITICAL: Replying to a review comment does NOT automatically resolve the thread. Thread resolution requires a separate GraphQL mutation.
After replying to a review comment, resolve the thread:
# Step 1: Reply to thread and resolve in one call
python3 .claude/skills/github/scripts/pr/add_pr_review_thread_reply.py \
--thread-id "PRRT_xxx" --body "Response text" --resolve
# Or as two separate steps:
# Step 1: Reply to comment
python3 .claude/skills/github/scripts/pr/add_pr_review_thread_reply.py \
--thread-id "PRRT_xxx" --body "Response text"
# Step 2: Resolve thread (REQUIRED separate step)
python3 .claude/skills/github/scripts/pr/resolve_pr_review_thread.py --thread-id "PRRT_xxx"
Why this matters: Replying to a comment does NOT automatically resolve the thread. Thread resolution requires a separate GraphQL mutation. Unresolved threads block PR merge per branch protection rules.
For 2+ threads, use the script with multiple thread IDs:
# Resolve multiple threads efficiently with reply + resolve
for thread_id in "PRRT_xxx" "PRRT_yyy" "PRRT_zzz"; do
python3 .claude/skills/github/scripts/pr/add_pr_review_thread_reply.py \
--thread-id "$thread_id" --body "Addressed." --resolve
done
# Or use GraphQL batch mutation for maximum efficiency (1 API call)
gh api graphql -f query='
mutation {
t1: resolveReviewThread(input: {threadId: "PRRT_xxx"}) { thread { id isResolved } }
t2: resolveReviewThread(input: {threadId: "PRRT_yyy"}) { thread { id isResolved } }
t3: resolveReviewThread(input: {threadId: "PRRT_zzz"}) { thread { id isResolved } }
}'
Benefits:
When reviewing PRs, consult these Serena memories for context:
| Memory | Purpose |
|---|---|
pr-review-007-merge-state-verification | GraphQL source of truth for merge state |
pr-review-004-thread-resolution-single | Single thread resolution via GraphQL |
pr-review-005-thread-resolution-batch | Batch thread resolution efficiency |
pr-review-008-session-state-continuity | Session context for multi-round reviews |
ai-quality-gate-failure-categorization | Infrastructure vs code quality failures |
session-log-fixer (skill) | Diagnose and fix session protocol failures |