Address GitHub PR review feedback at line, file, and PR level — fetch, evaluate, fix, commit, and resolve. Use when given a PR URL, a review-comment URL, or a PR-level comment URL. Triggers: GitHub discussion URLs, "fix this review comment", "address PR feedback", "resolve PR comments", PR URLs with #discussion_, #issuecomment-, or #pullrequestreview- fragments.
95%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
Address GitHub PR review comments: fetch the comment, evaluate the fix, apply it, commit, and resolve.
URL contains #discussion_r<id> — address one specific comment.
URL points to the PR itself — iterate every unresolved line-level thread, file-level thread, and PR-level comment.
URL contains #issuecomment-<id> (a PR-level comment) or #pullrequestreview-<id>
(a review body) — address one PR-level finding via the PR-Level Flow below.
Extract owner, repo, PR number, and comment ID from the URL.
gh api repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id} --jq '{
id: .id,
path: .path,
line: .line,
side: .side,
body: .body,
diff_hunk: .diff_hunk,
subject_type: .subject_type,
user: .user.login
}'Retain the original body from this fetch. If the original comment is accidentally edited instead of replied to, the original finding text can be restored by PATCHing this saved body back to the comment.
If the comment includes a suggested code change (GitHub suggestion block in the body), extract the suggested code.
Apply the receiving-code-review skill principles:
AskUserQuestion before changing code.Read the file at path, make the change with Edit.
Stage only the changed file(s) and commit using /git:commit conventions:
fix: <concise description of what the review comment asked for>Reply to the thread — never edit the original comment. The resolution message is a NEW comment in the thread, posted via the replies endpoint. The reviewer's original finding text must remain intact.
gh api repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id}/replies \
-f body="Resolved by <sha>"<sha> is the commit SHA from step 4 (git rev-parse --short HEAD after committing). This leaves an audit trail in the thread linking the resolution to its fix before the thread is collapsed.
FORBIDDEN — this REPLACES the original review comment's body:
gh api repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id} \
--method PATCH -f body="Resolved by <sha>"Also forbidden: gh pr comment --edit-last and any MCP "update comment" tool for this purpose — the reply MUST go through the replies endpoint.
Self-check after posting: the response JSON is a new comment whose id differs from {comment_id}. If the response id equals {comment_id}, the original comment was edited — restore it immediately by PATCHing the original body back (from step 1's saved body), then redo step 5 with the replies endpoint.
Capture the reply's .html_url for the final report.
Mandatory — the run is incomplete until the thread is resolved. Posting the reply (step 5) is not sufficient; a thread left open will be re-processed by the next run.
Mark the review thread as resolved:
gh api graphql -f query='
mutation {
resolveReviewThread(input: {threadId: "<thread_node_id>"}) {
thread { isResolved }
}
}
'Confirm the response contains isResolved: true before moving on. If the mutation errors (permissions, stale thread), report it in the final summary under "Needs discussion" — never silently continue past a failed resolve.
To get the thread node ID from a comment:
gh api repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id} --jq '.node_id'Then find the thread it belongs to:
gh api graphql -f query='
query {
node(id: "<comment_node_id>") {
... on PullRequestReviewComment {
pullRequestReviewThread { id isResolved }
}
}
}
'Note for All-Comments Flow: the thread id is already present in the reviewThreads query output (step 1 of All-Comments Flow) — skip both lookup calls and run the mutation directly.
Fixed: <file>:<line> — <what changed>
Committed: <short sha> <commit message>
Reply: <reply comment url>
Resolved: comment by <user> — thread isResolved: trueInclude the reply URL (from step 5's response .html_url) and the literal isResolved: true confirmation so a skipped step 6 leaves a visible gap in the report.
gh api graphql -f query='
query {
repository(owner: "{owner}", name: "{repo}") {
pullRequest(number: {pr}) {
reviewThreads(first: 100) {
nodes {
id
isResolved
isOutdated
subjectType
path
line
comments(first: 10) {
nodes {
id
databaseId
path
line
body
diffHunk
author { login }
createdAt
}
}
}
}
}
}
}
'Filter to threads where isResolved is false. Both subjectType values are in
scope and handled identically — a FILE thread is feedback on the file as a whole
(review comments made outside any diff line) and it resolves through
resolveReviewThread exactly like a LINE thread.
line: null does not mean file-level. It means the thread is outdated
(isOutdated: true) because the diff moved under it; subjectType is still LINE.
For those, locate the code by the path plus the quoted diff_hunk from the
comment body rather than by line number.
For each unresolved thread, apply the Single Comment Flow (steps 2-7).
Each fix gets its own commit. Do NOT batch fixes into one commit — one comment = one commit.
After processing all line/file threads, process PR-level comments and reviews via the PR-Level Flow below.
After all threads and PR-level findings are processed, push the branch:
git pushAfter processing all threads and PR-level findings:
## PR Feedback Summary
Line/file threads — resolved: N
- <path>:<line> [LINE|FILE] — <description> (<sha>)
PR-level comments — resolved: N
- <path>:<line> — <description> (<sha>) [minimized|replied, not minimized]
Skipped: M
- <locator> — <reason> (by <user>)
Already handled: K
- <locator> — <minimize marker | resolution claim in <url>, verified>
Needs discussion: P
- <locator> — <question or options>PR-level feedback lives on two surfaces, neither threaded, so resolveReviewThread
does not apply to either.
Fetch both in one call:
gh api graphql -f query='
query {
repository(owner: "{owner}", name: "{repo}") {
pullRequest(number: {pr}) {
comments(first: 100) {
nodes {
id
databaseId
body
createdAt
isMinimized
minimizedReason
viewerCanMinimize
author { login }
url
}
}
reviews(first: 100) {
nodes {
id
body
state
createdAt
isMinimized
minimizedReason
viewerCanMinimize
author { login }
url
}
}
}
}
}
'Every comments node is a candidate finding. A reviews node is a candidate only
when body is non-empty — a review with an empty body is just the envelope around
inline comments already covered by the thread flow.
Single-target fetches for Mode 3:
gh api repos/{owner}/{repo}/issues/comments/{comment_id} # #issuecomment-<id>, node_id starts IC_
gh api repos/{owner}/{repo}/pulls/{pr}/reviews/{review_id} # #pullrequestreview-<id>, node_id starts PRR_Document the resolution-detection ladder, in order, stopping at the first hit:
isMinimized: true means already handled — this skill sets
it in step 4 below. Skip the finding and carry minimizedReason into the report.createdAt strictly greater than the
finding's for a claim that resolves or defers it. A body counts as a claim when it
references the finding and carries a resolution or deferral signal:
#issuecomment-<id> anchor, or
a distinctive symbol name quoted in the finding.#<number> reference together
with any of resolved, fixed, addressed, landed, implemented.deferred, won't fix, wontfix, out of scope,
tracked in, follow-up.#<number> tracker exists) and report it under Skipped instead of reprocessing.Locator footer: review tooling that reposts an out-of-diff finding at PR level appends a footer naming the original location, in either of these forms (verified on real comments):
_Originally at: https://github.com/<owner>/<repo>/blob/<sha>/<path>#L82-L88_
_Reviewed against https://github.com/<owner>/<repo>/blob/<sha>/<path>#L214-L215_Parse <path> (everything after blob/<sha>/) and the #L<start>[-L<end>] range to
locate the code. Absent a footer, locate from the body text.
Marking a PR-level finding handled — two calls, in this order, replacing thread
reply + resolveReviewThread:
# 1. Reply. There is no thread, so the comment must identify which finding it answers.
gh api repos/{owner}/{repo}/issues/{pr}/comments \
-f body="Resolved by <sha> — <path>:<line> <one-line finding summary>"
# 2. Mark it resolved. subjectId is the GraphQL node id (IC_… or PRR_…), not the databaseId.
gh api graphql -f query='
mutation {
minimizeComment(input: {subjectId: "<node_id>", classifier: RESOLVED}) {
minimizedComment { isMinimized minimizedReason }
}
}
'Both IssueComment and PullRequestReview support minimizeComment. If
viewerCanMinimize is false, post the reply, skip the mutation, and record the
finding as "replied, not minimized" in the report — never fail the run over it.
receiving-code-review principles — no blind implementation.reviewThreads line comments silently
misses it.resolveReviewThread does not
apply. Post an identifying Resolved by <sha> comment, then
minimizeComment(classifier: RESOLVED) so a later run detects it without
re-scanning prose.POST .../comments/{id}/replies). Never PATCH the original comment, never gh pr comment --edit-last — overwriting the reviewer's finding destroys the audit trail.resolveReviewThread returns isResolved: true. Posting the reply alone leaves the conversation open for re-processing.phatblat/dotfiles
4d65cab
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.