PR Review Workflow (gh)
Use this skill when you need to create a PR, pull review comments, fix them
incrementally, and reply to each comment individually using gh.
Quick Workflow
- •Create/update PR
- •
gh pr create --fill --body-file /tmp/pr_body.txt - •
gh pr edit --body-file /tmp/pr_body.txt
- •Fetch review comments
- •Summary/comments:
gh pr view <PR> --comments - •Structured data:
gh api repos/{owner}/{repo}/pulls/<PR>/comments - •Pagination (recommended for large threads): add
--paginate - •Skip resolved threads by ignoring any comment that already has a reply
(a matching
in_reply_to_idexists) or that is marked resolved in the UI. - •Unresolved-only JSON (ignore replied-to threads):
bash
python - <<'PY'
import json, subprocess
raw = subprocess.check_output(
["gh", "api", "--paginate", "repos/{owner}/{repo}/pulls/<PR>/comments"],
text=True,
)
comments = json.loads(raw)
replied_to = {c.get("in_reply_to_id") for c in comments if c.get("in_reply_to_id")}
unresolved = [
{
"id": c.get("id"),
"html_url": c.get("html_url"),
"path": c.get("path"),
"body": c.get("body"),
}
for c in comments
if c.get("in_reply_to_id") is None and c.get("id") not in replied_to
]
print(json.dumps(unresolved, indent=2))
PY
- •Unresolved-only JSON (jq):
bash
gh api --paginate repos/{owner}/{repo}/pulls/<PR>/comments \
| jq '[. as $all
| ($all | map(select(.in_reply_to_id != null) | .in_reply_to_id) | unique) as $replied
| $all[]
| select(.in_reply_to_id == null and (.id | IN($replied[]) | not))
| {id, html_url, path, body}]'
- •Fix review notes commit-by-commit
- •For each comment:
- •identify the target commit (use
commit_idfrom the review payload, orgit log -- <file>/git blame) - •start
git rebase -i <base>and mark the target commitedit - •apply the fix
- •run validation before committing (normally
make lint,make unit, and any relevant itests) - •if the change affects scripts or build tooling, lint/unit may need to be omitted; ask the user and follow their direction
- •
git add <files> - •
git commit -m "! <short change>" - •continue the rebase, resolving conflicts if needed
- •repeat until all notes addressed
- •identify the target commit (use
- •Ensure fixes land in the commit the reviewer commented on:
- •Keep the fix commit directly after the original commit.
- •Confirm the diff for the addressed commit is updated.
- •Test after fixes
- •Run extensive tests after applying fixes to ensure correctness.
- •Prefer
make lintand relevant unit/integration tests. - •Run per-fix validation before committing each change.
- •After all fixes, run a final validation pass on the full branch.
- •Note any tests not run in the PR response.
- •Reply to each comment individually (draft replies only)
- •Always create or reuse a pending review and attach draft replies to it.
- •Create a pending review (omit
eventto keep it draft):- •
gh api -X POST repos/{owner}/{repo}/pulls/<PR>/reviews -F body='Draft replies'
- •
- •Add a draft reply to the pending review with GraphQL (supports replies):
- •
gh api graphql -f query='mutation($pullRequestId:ID!,$pullRequestReviewId:ID!,$inReplyTo:ID!,$body:String!){addPullRequestReviewComment(input:{pullRequestId:$pullRequestId,pullRequestReviewId:$pullRequestReviewId,inReplyTo:$inReplyTo,body:$body}){comment{url body}}}' \ -f pullRequestId=<PR_NODE_ID> \ -f pullRequestReviewId=<REVIEW_NODE_ID> \ -f inReplyTo=<COMMENT_NODE_ID> \ -f body='Acked; updated to ...'
- •
- •If a pending review already exists, reuse it; do not submit the review.
- •Reply content: describe what changed and how, without referencing commit
hashes, rebases, or internal workflow mechanics. Example:
- •“Fixed by changing the default case to return
FailedStatewith an explicit reason, so we fail closed on unexpected statuses.”
- •“Fixed by changing the default case to return
Comment Formatting Rules
- •Use real newlines, not literal
\n. - •Do not manually hard-wrap paragraphs to 72/80 columns. GitHub renders and wraps comments/PR descriptions correctly in the UI; keep lines natural.
- •For multi-line replies, prefer a heredoc:
bash
gh api -X POST repos/{owner}/{repo}/pulls/<PR>/comments \
-F in_reply_to=<COMMENT_ID> \
-F body=@- <<'EOF'
Applied fix:
- updated X
- refactored Y
EOF
- •If using a file, prefer
-F body=@filerather than shell string interpolation, to preserve newlines safely:
bash
gh api -X POST repos/{owner}/{repo}/issues/<PR>/comments \
-F body=@/tmp/comment.md
- •Keep replies concise and specific to the change made.
Fetch and Respond Example
- •
Find comment IDs:
gh api repos/{owner}/{repo}/pulls/<PR>/comments - •
Reply:
gh api -X POST repos/{owner}/{repo}/pulls/<PR>/comments \ -F in_reply_to=<COMMENT_ID> \ -F body='Updated sh() to use argv lists and removed shell=True.'
Notes
- •
gh pr view <PR> --commentsis quick for a read-only pass. - •Use
gh api .../pulls/<PR>/commentsto getidvalues for replies. - •Keep each review response tied to its specific thread.