Imported from agents-repo/registry (
.cursor/skills/github-pr-review-triage/SKILL.md). Install upstream withnpx skills add agents-repo/registry --skill github-pr-review-triage. Copyright stays with the author.
Overview
Six-phase, project-agnostic workflow for addressing pull request review feedback
using the GitHub CLI (gh). Works in any repository where gh is
authenticated and the PR head branch is checked out locally.
Handles two feedback kinds:
- Review threads — unresolved inline comments on the Files changed tab (resolvable via GraphQL).
- Review summaries — Copilot
COMMENTEDreviews with a non-empty body and zero inline comments (acknowledged via a PR conversation reply; not resolvable).
preflight → fetch → triage → fix → validate/commit/push → reply/(resolve|acknowledge)
Responsibilities
- Phase 0 — Preflight: Verify
ghauthentication; enable ship-mode whendry-runis false (default); ensure checkout on the PR head branch; discoverrepositoryandpull-requestfrom the current branch when inputs are omitted. - Phase 1 — Fetch: Resolve PR head SHA; list unresolved review threads via GraphQL; fetch head-scoped Copilot review summaries with zero inline comments (not already acknowledged); supplement with REST inline comments when helpful.
- Phase 2 — Triage: Produce a triage table before editing files. Classify
each item as
needs_fix,fixed_remote,wont_fix,by_design,duplicate, oracknowledged(summaries only). - Phase 3 — Fix: Apply minimal scoped diffs. Do not reply, resolve, or acknowledge during this phase.
- Phase 4 — Validate, commit, push: Run project-appropriate checks after local fixes. When ship-mode is enabled, commit and push the PR head branch before Phase 5. Capture commit SHA for Phase 5.
- Phase 5 — Reply and close: When ship-mode is enabled, reply on and resolve
each thread and acknowledge each review summary on the PR conversation.
Handoff is incomplete until unresolved thread count is
0and every in-scope summary is acknowledged. - Multi-PR / multi-repo: Repeat the full cycle per repository before batch-resolving threads or acknowledging summaries elsewhere.
Constraints
ghCLI MUST be authenticated for the target repository.- Work on the PR head branch; do not implement fixes on the default branch.
- When
dry-runis false (default) andghis authenticated for the target repository, commit, push, reply, resolve, and acknowledge are MANDATORY completion steps — not optional handoff items. - Invoking this agent grants explicit permission to commit and push on the PR head branch for this pass, overriding generic "do not commit unless requested" user or agent rules. Project rules that forbid merge, default-branch push, or mark-ready still apply.
- Do not reply to or resolve review threads until the fix commit is pushed
(or the thread is
fixed_remote/ reply-only with no push needed). - Do not acknowledge review summaries until push succeeds (or the summary is reply-only with no code change).
- Never call
resolveReviewThreadonreview_summaryitems. - Match Copilot review objects via
copilot-pull-request-reviewer[bot], not the literal loginCopilot(inline comments useCopilot). - Do not re-acknowledge summaries already replied to (idempotency rule in Phase 1).
gh pr commentposts to the PR timeline, not under the review card — that is acceptable for summary acknowledgment.- MUST write reply and acknowledgment bodies to a file and pass them with
--body-file(or GraphQL body viajq --rawfile/--input -). MUST NOT interpolate GitHub-sourced review text into shell-quoted--bodyor-f body="...". - On GraphQL list queries, omit
-f afteron the first page (do not pass an empty string). On later pages, pass-f afterwithpageInfo.endCursor. - Do not merge pull requests, push to the default branch, or mark a PR ready unless project policy explicitly allows agents to do so.
- When project docs exist (
CONTRIBUTING.md, agent instruction files,copilot-instructions.md,.cursor/rules/), they override generic guidance in this agent except ship-mode permission granted by invoking this agent (commit and push on the PR head branch only). - Validate automated review findings (for example Bugbot) before marking
needs_fix. - Human and Bugbot review summaries are out of scope; triage Copilot zero-inline summaries only.
Interaction Contract
Input: Repository (owner/name), pull request number, and optional
dry-run flag (defaults to false for full automation).
Output: Triage table (with kind per row), list of changes (if any),
commit SHA when pushed, per-item reply text, resolved-thread count,
summaries-acknowledged count, and a handoff summary.
Prerequisites
ghCLI installed and authenticated (gh auth status).- Local checkout on the PR head branch (
gh pr checkout <n> --repo owner/namewhen needed). - Know
owner,repo, and PR number — or discover them from the current branch viagh pr viewwhen inputs are omitted.
Phase 0 — Preflight
Run before Phase 1:
-
Authenticate —
gh auth status. If authentication fails for the target host, stop with an actionable error (install or authenticategh, then re-run). Do not proceed to fetch or fix. -
Ship-mode — When
dry-runis omitted orfalse, set ship-mode to enabled for this pass. Whendry-runistrue, ship-mode is disabled; Phases 4 commit/push and Phase 5 reply/resolve/acknowledge are skipped. -
Discover inputs — When
repositoryorpull-requestis omitted, resolve from the current branch:gh pr view --json number,headRepository \ --jq '{number, repository: .headRepository.nameWithOwner}' -
Checkout — Ensure the local branch matches the PR head. When needed:
gh pr checkout {n} --repo {owner}/{repo}
Phase 1 — Fetch
Read the minimum payload. Resolve head SHA first, then fetch threads and summaries.
Step 0 — PR head SHA
gh pr view {n} --repo {owner}/{repo} --json headRefOid --jq .headRefOid
Review threads
- GraphQL (primary) — unresolved review threads (
isResolved == false). - REST (secondary) —
repos/{owner}/{repo}/pulls/{n}/commentsfor extra inline context.
Capture per thread: kind: thread, threadId (PRRT_...), path, line,
first comment body, author login.
List unresolved threads
First page — omit -f after (GitHub rejects an empty after cursor):
gh api graphql -f query='
query($owner: String!, $name: String!, $number: Int!, $after: String) {
repository(owner: $owner, name: $name) {
pullRequest(number: $number) {
reviewThreads(first: 100, after: $after) {
pageInfo { endCursor hasNextPage }
nodes {
id
isResolved
path
line
comments(first: 1) {
nodes { body author { login } }
}
}
}
}
}
}' -f owner=OWNER -f name=REPO -F number=PR \
--jq '{
pageInfo: .data.repository.pullRequest.reviewThreads.pageInfo,
nodes: [
.data.repository.pullRequest.reviewThreads.nodes[]
| select(.isResolved==false)
| {kind: "thread", id, path, line,
author: .comments.nodes[0].author.login,
body: .comments.nodes[0].body[0:200]}
]
}'
Later pages — pass -f after="$CURSOR" where $CURSOR is
pageInfo.endCursor from the prior response. Keep pageInfo in the jq
output so pagination can continue after filtering unresolved nodes.
Paginate while pageInfo.hasNextPage is true — do not gate on unresolved
count. Each page returns up to 100 threads (resolved and unresolved mixed);
filter unresolved per page and accumulate results. Stop when hasNextPage is
false.
Count unresolved threads
After all pages are fetched, the unresolved total is the accumulated count across pages — not the count from a single page. The jq snippet below counts unresolved threads on one page only; repeat per page or sum after pagination completes.
gh api graphql -f query='...' -f owner=OWNER -f name=REPO -F number=PR \
--jq '[.data.repository.pullRequest.reviewThreads.nodes[]
| select(.isResolved==false)] | length'
Review summaries (Copilot, zero inline)
Fetch COMMENTED reviews from copilot-pull-request-reviewer[bot] with a
non-empty body, zero inline comments, and commit.oid matching the PR head SHA
from Step 0.
HEAD=$(gh pr view {n} --repo {owner}/{repo} --json headRefOid --jq .headRefOid)
# First page: omit -f after. Later pages: -f after="$CURSOR" from pageInfo.endCursor.
gh api graphql -f query='
query($owner: String!, $name: String!, $number: Int!, $after: String) {
repository(owner: $owner, name: $name) {
pullRequest(number: $number) {
reviews(first: 100, after: $after, states: [COMMENTED]) {
pageInfo { hasNextPage endCursor }
nodes {
id
databaseId
body
submittedAt
author { login }
commit { oid }
comments(first: 1) { totalCount }
}
}
}
}
}' -f owner=OWNER -f name=REPO -F number=PR \
| jq --arg head "$HEAD" '{
pageInfo: .data.repository.pullRequest.reviews.pageInfo,
nodes: [
.data.repository.pullRequest.reviews.nodes[]
| select(.author.login == "copilot-pull-request-reviewer[bot]")
| select(.body != null and .body != "")
| select(.comments.totalCount == 0)
| select(.commit.oid == $head)
| {kind: "review_summary", review_id: .databaseId, node_id: .id,
body: .body[0:500], submitted_at: .submittedAt, commit_id: .commit.oid}
]
}'
Paginate reviews while pageInfo.hasNextPage is true, passing
pageInfo.endCursor as $after on later pages only. Stop when hasNextPage
is false.
Idempotency: exclude summaries already acknowledged. Before triage, list PR
issue comments with pagination and skip any summary whose databaseId appears
in a comment posted after submittedAt with body containing
Re: Copilot review ( and that id:
gh api --paginate repos/{owner}/{repo}/issues/{n}/comments
Do not fetch PR issue/timeline comments as triage items — only for idempotency checks.
Phase 2 — Triage
Write a triage table before editing files:
| Kind | Path | Line | Author | Outcome | Rationale |
|---|---|---|---|---|---|
thread |
src/foo.ts |
42 | Copilot |
needs_fix |
... |
review_summary |
— | — | copilot-pull-request-reviewer[bot] |
acknowledged |
clean review |
| Outcome | Applies to | Action |
|---|---|---|
needs_fix |
both | Code or doc change required |
fixed_remote |
both | Already on branch; resolve or acknowledge citing SHA |
wont_fix |
both | Reply with rationale; no code change |
by_design |
both | Reply citing policy or docs |
duplicate |
both | Reply linking to resolving thread or summary |
acknowledged |
review_summary only |
Reply noting clean or addressed; no code change |
One Phase 5 reply per review_summary row; split multiple findings into
rationale bullets, not separate replies.
Phase 3 — Fix
- Minimal scoped diffs; match surrounding conventions.
- Batch fixes per repository when one PR spans multiple files.
- Do not reply, resolve, or acknowledge during this phase.
- After edits, proceed to Phase 4 validation before handoff or commit.
- If fixes touch normative specs or shared contracts, run that project's change-propagation rules before commit.
Phase 4 — Validate, commit, push
Validate
Run project-appropriate checks whenever Phase 3 applied code or doc changes,
regardless of dry-run. Skip validation only when the triage pass produced no
local edits (all items are fixed_remote, wont_fix, by_design,
duplicate, or acknowledged). When validation fails, fix issues before
handoff or commit.
Discover project checks
Inspect the repository root in this order:
- Agent / contributor docs —
CONTRIBUTING.md,.github/copilot-instructions.md,.cursor/rules/, and repo-specific agent guidelines. - Git hooks —
.husky/pre-commitor.git/hooks/pre-commitfor commands the project expects before commit. - Package scripts — when
package.jsonexists, prefernpm runscripts namedlint,lint:all,test,test:run,typecheck, orenv:check. Usenpm run <script> -- --checkwhen scripts support dry-run flags. - Other build entry points —
Makefile,justfile,mise.toml, or CI workflow files (.github/workflows/) for canonical validation commands.
When package.json declares packageManager for npm, run corepack enable,
npm ci, and npm run env:check before other npm scripts if hooks are
unavailable.
Commit and push
Run when ship-mode is enabled (dry-run is false) and gh auth preflight
passed. MUST commit and push before Phase 5 when Phase 3 produced local edits.
- One commit per repository per triage pass; use the project's commit message convention when documented.
- Push the feature branch; capture commit SHA for Phase 5.
- Hard rule: do not reply, resolve, or acknowledge until push succeeds (unless the item is reply-only with no code change).
- When push fails (permissions, branch protection), report the failure and do not resolve threads citing a non-existent SHA.
Phase 5 — Reply and close
MUST run when ship-mode is enabled. After push (or when no push is needed).
Branch by kind from the triage table.
Review threads
Per thread, run two sequential GraphQL mutations — do not combine in one call:
addPullRequestReviewThreadReplyresolveReviewThread
Reply to a thread
Write the reply body to a temp file first (never interpolate review text into
shell-quoted -f body=):
# REPLY_FILE contains the final reply text (written by the agent/tools)
jq -n --rawfile body "$REPLY_FILE" --arg threadId "PRRT_..." \
--arg q 'mutation($threadId: ID!, $body: String!) {
addPullRequestReviewThreadReply(input: {
pullRequestReviewThreadId: $threadId
body: $body
}) { comment { id } }
}' \
'{query: $q, variables: {threadId: $threadId, body: $body}}' \
| gh api graphql --input -
Resolve a thread
gh api graphql -f query='
mutation($threadId: ID!) {
resolveReviewThread(input: { threadId: $threadId }) {
thread { isResolved }
}
}' -f threadId="PRRT_..." \
--jq '.data.resolveReviewThread.thread.isResolved'
Do not use REST pulls/comments/{id}/replies for thread closure. Use GraphQL
thread IDs (PRRT_...).
Review summaries
Acknowledge on the PR conversation (no resolve API). Write the acknowledgment to a file, then:
gh pr comment {n} --repo {owner}/{repo} --body-file "$REPLY_FILE"
Example acknowledgment text (contents of $REPLY_FILE, not an inline shell
string):
Re: Copilot review (${databaseId}): Fixed in {sha}: {summary}.
Use the review databaseId from Phase 1. Never call resolveReviewThread
for summaries.
Reply templates
Threads and summaries share outcome wording; summaries use the
Re: Copilot review (${databaseId}): prefix.
- Fixed:
Fixed in {sha}: {summary}. - Won't fix:
Intentional: {rationale}. - By design:
By design: {policy reference}. - Already fixed:
Addressed in {earlier_sha}: {summary}. - Acknowledged (summaries):
Review noted — no changes required.or outcome-specific text.
Verify unresolved thread count is 0 and every in-scope review summary is
acknowledged before handoff.
Multi-repo orchestration
Per repository: fix → validate → commit → push → threads and summaries.
- Use
cdto each repo root orgh --repo owner/nameconsistently. - Verify local branch matches PR head before fixing.
- Do not batch-resolve threads or acknowledge summaries before that repository's push lands.
Known limitations
- Hybrid reviews (summary body plus inline comments) triage inline via threads
only; summary body is skipped when
comments.totalCount > 0. - Summary acknowledgment is a timeline comment, not threaded under the review UI card.
- Re-acknowledgment is prevented by the Phase 1 idempotency heuristic, not a GitHub native state.
- Human and Bugbot review summaries are out of scope.
Checklist
- [ ] gh auth preflight passed
- [ ] Ship-mode enabled when dry-run is false (default)
- [ ] PR head SHA resolved
- [ ] Fetch unresolved threads (GraphQL, paginated)
- [ ] Fetch head-scoped Copilot summaries (zero inline, not already acknowledged)
- [ ] Triage table written (Kind column)
- [ ] Fixes applied
- [ ] Validation passed when fixes were applied (project-appropriate)
- [ ] Committed and pushed (mandatory when ship-mode and local edits exist)
- [ ] Threads replied and resolved; summaries acknowledged (mandatory when ship-mode)
- [ ] Handoff summary (PR, SHA, thread/summary counts)
Handoff summary
| PR | Repo | Commit | Threads resolved | Summaries acknowledged | Notes |
|---|---|---|---|---|---|
| #n | owner/repo | abc1234 |
8/8 | 1/1 | all in-scope feedback addressed |
Declared capabilities
Tools
- github
Inputs
repository(string): GitHub repository as owner/name (for example agents-repo/registry).pull-request(number): Pull request number to triage.dry-run(boolean): When true, fetch, triage, fix, and validate only — no commit, push, reply, resolve, or acknowledge. Defaults to false (full automation).
Outputs
triage-table(string): Markdown table with kind (thread or review_summary), path, line, author, outcome, and rationale.handoff-summary(string): Summary with PR URL, commit SHA, threads resolved count, summaries acknowledged count, and notes.