PR Review Gate
Rosetta's branch protection on main requires all conversation threads resolved,
enforced independently of status checks. CodeRabbit and Copilot both submit COMMENTED
reviews (never CHANGES_REQUESTED), so reviewDecision stays clean and checks show green
while the PR is still mergeStateStatus: BLOCKED by an open bot thread. Full rationale:
.github/instructions/pr-review-bots.instructions.md — this skill is the runnable
procedure; read that file for the "why."
When to use
- A PR just went Ready for Review, or you pushed a fix commit, and you're about to tell the
user the PR is done/mergeable.
gh pr merge refuses despite green checks.
- The user asks to address, triage, or close out review comments.
When NOT to use
- Draft PRs — bots don't review drafts.
- As a substitute for requesting a fresh bot pass. Re-review costs money (Copilot) or burns
a rate-limited slot (CodeRabbit: 1-3/hour) — only request one (
@coderabbitai review PR
comment, or re-request the Copilot reviewer) for fixes big/ambiguous enough to warrant a
second look. A one-line string fix doesn't need it; resolve the thread and move on.
Procedure
Resolve the PR and repo.
gh pr view --json number,url,headRefName # or: gh pr view <N> if given
gh repo view --json owner,name --jq '.owner.login + "/" + .name'
Read the real gate, not reviewDecision.
gh pr view <N> --json mergeStateStatus,reviewDecision,statusCheckRollup
mergeStateStatus: BLOCKED with green statusCheckRollup and clean reviewDecision
means unresolved threads — proceed to step 3. CLEAN means you're done; skip to step 6.
List unresolved threads (GraphQL — the flat comment list hides resolution state).
gh api graphql -f query='query{repository(owner:"OWNER",name:"REPO"){pullRequest(number:N){reviewThreads(first:100){nodes{id isResolved isOutdated path line}}}}}' \
--jq '.data.repository.pullRequest.reviewThreads.nodes[]|select(.isResolved==false)|"\(.id)\t\(.path):\(.line)"'
Page with after: cursors if a PR has more than 100 threads. The unresolved count is
often smaller than the raw comment count — GitHub auto-resolves some threads when the
anchored lines move; don't reconcile against pulls/N/comments, only against this list.
Get comment bodies for the unresolved threads.
gh api --paginate repos/OWNER/REPO/pulls/N/comments \
--jq '.[] | "\(.user.login) \(.path):\(.line // .original_line)\n\(.body)\n"'
For each unresolved thread: fix it, or dismiss it with a grounded reason.
- Fix: make the code change locally.
- Dismiss: only when the finding is wrong or out of scope — say why.
- Don't resolve a thread opened by a human reviewer on their behalf; you own bot
threads (Copilot/CodeRabbit) once handled.
- Replying does not resolve — resolving is a separate mutation:
gh api graphql -f query='mutation($id:ID!){resolveReviewThread(input:{threadId:$id}){thread{isResolved}}}' -f id=THREAD_ID
Reply with the fixing commit SHA before resolving, for an audit trail.
After pushing fixes, re-check — don't assume the push resolved anything.
- A fix push does not auto-resolve the thread it fixes; it may flip to
isOutdated,
which looks handled in the UI but is not isResolved.
- The push also triggers a fresh CodeRabbit pass (and possibly Copilot) that can open
new threads. Wait for that re-review to settle, then repeat step 3.
- Loop steps 3-6 until the unresolved list is empty.
Confirm the gate is actually clear.
gh pr view <N> --json mergeStateStatus
Only report the PR as mergeable when this reads CLEAN (or UNSTABLE/etc. for reasons
unrelated to review threads — say which).
Output format
PR #104 — mergeStateStatus: BLOCKED -> resolving 3 unresolved threads
1. src/query.ts:42 (coderabbitai) — fixed in a1b2c3d, resolved
2. src/mcp.ts:88 (copilot-pull-request-reviewer) — fixed in a1b2c3d, resolved
3. README.md:12 (coderabbitai) — dismissed (stale suggestion, already matches docs), resolved
Re-review settled, no new threads. mergeStateStatus: CLEAN. Mergeable.
What this skill does NOT do
- Merge the PR — that's a separate explicit action.
- Request a fresh bot review automatically — ask the user first; it costs money or burns a
rate-limited slot (see "When NOT to use").
- Resolve a human reviewer's thread on their behalf.
1---2name: pr-review-gate3description: Close the loop on a rosetta PR's bot review threads (CodeRabbit + Copilot) before claiming it's mergeable. Green status checks and a clean reviewDecision do NOT mean mergeable here — main requires all conversation threads resolved, and that's a separate gate. Use when a PR is Ready for Review and you're about to say it's done, when `gh pr merge` is blocked despite green checks, or when the user asks to address/close review comments.4---56# PR Review Gate78Rosetta's branch protection on `main` requires **all conversation threads resolved**,9enforced independently of status checks. CodeRabbit and Copilot both submit `COMMENTED`10reviews (never `CHANGES_REQUESTED`), so `reviewDecision` stays clean and checks show green11while the PR is still `mergeStateStatus: BLOCKED` by an open bot thread. Full rationale:12`.github/instructions/pr-review-bots.instructions.md` — this skill is the runnable13procedure; read that file for the "why."1415## When to use1617- A PR just went Ready for Review, or you pushed a fix commit, and you're about to tell the18 user the PR is done/mergeable.19- `gh pr merge` refuses despite green checks.20- The user asks to address, triage, or close out review comments.2122## When NOT to use2324- Draft PRs — bots don't review drafts.25- As a substitute for requesting a fresh bot pass. Re-review costs money (Copilot) or burns26 a rate-limited slot (CodeRabbit: 1-3/hour) — only request one (`@coderabbitai review` PR27 comment, or re-request the Copilot reviewer) for fixes big/ambiguous enough to warrant a28 second look. A one-line string fix doesn't need it; resolve the thread and move on.2930## Procedure31321. **Resolve the PR and repo.**3334 ```sh35 gh pr view --json number,url,headRefName # or: gh pr view <N> if given36 gh repo view --json owner,name --jq '.owner.login + "/" + .name'37 ```38392. **Read the real gate, not `reviewDecision`.**4041 ```sh42 gh pr view <N> --json mergeStateStatus,reviewDecision,statusCheckRollup43 ```4445 `mergeStateStatus: BLOCKED` with green `statusCheckRollup` and clean `reviewDecision`46 means unresolved threads — proceed to step 3. `CLEAN` means you're done; skip to step 6.47483. **List unresolved threads (GraphQL — the flat comment list hides resolution state).**4950 ```sh51 gh api graphql -f query='query{repository(owner:"OWNER",name:"REPO"){pullRequest(number:N){reviewThreads(first:100){nodes{id isResolved isOutdated path line}}}}}' \52 --jq '.data.repository.pullRequest.reviewThreads.nodes[]|select(.isResolved==false)|"\(.id)\t\(.path):\(.line)"'53 ```5455 Page with `after:` cursors if a PR has more than 100 threads. The unresolved count is56 often smaller than the raw comment count — GitHub auto-resolves some threads when the57 anchored lines move; don't reconcile against `pulls/N/comments`, only against this list.58594. **Get comment bodies for the unresolved threads.**6061 ```sh62 gh api --paginate repos/OWNER/REPO/pulls/N/comments \63 --jq '.[] | "\(.user.login) \(.path):\(.line // .original_line)\n\(.body)\n"'64 ```65665. **For each unresolved thread: fix it, or dismiss it with a grounded reason.**67 - Fix: make the code change locally.68 - Dismiss: only when the finding is wrong or out of scope — say why.69 - Don't resolve a thread opened by a *human* reviewer on their behalf; you own bot70 threads (Copilot/CodeRabbit) once handled.71 - Replying does not resolve — resolving is a separate mutation:7273 ```sh74 gh api graphql -f query='mutation($id:ID!){resolveReviewThread(input:{threadId:$id}){thread{isResolved}}}' -f id=THREAD_ID75 ```7677 Reply with the fixing commit SHA before resolving, for an audit trail.78796. **After pushing fixes, re-check — don't assume the push resolved anything.**80 - A fix push does **not** auto-resolve the thread it fixes; it may flip to `isOutdated`,81 which looks handled in the UI but is not `isResolved`.82 - The push also triggers a fresh CodeRabbit pass (and possibly Copilot) that can open83 **new** threads. Wait for that re-review to settle, then repeat step 3.84 - Loop steps 3-6 until the unresolved list is empty.85867. **Confirm the gate is actually clear.**8788 ```sh89 gh pr view <N> --json mergeStateStatus90 ```9192 Only report the PR as mergeable when this reads `CLEAN` (or `UNSTABLE`/etc. for reasons93 unrelated to review threads — say which).9495## Output format9697```text98PR #104 — mergeStateStatus: BLOCKED -> resolving 3 unresolved threads991001. src/query.ts:42 (coderabbitai) — fixed in a1b2c3d, resolved1012. src/mcp.ts:88 (copilot-pull-request-reviewer) — fixed in a1b2c3d, resolved1023. README.md:12 (coderabbitai) — dismissed (stale suggestion, already matches docs), resolved103104Re-review settled, no new threads. mergeStateStatus: CLEAN. Mergeable.105```106107## What this skill does NOT do108109- Merge the PR — that's a separate explicit action.110- Request a fresh bot review automatically — ask the user first; it costs money or burns a111 rate-limited slot (see "When NOT to use").112- Resolve a human reviewer's thread on their behalf.