Comprehensive workflow for managing GitHub PR review comments using gh CLI and GraphQL API. Use when asked to address review comments, find unreplied comments, reply to review threads, or resolve/unresolve review conversations. Supports finding ALL comments across pagination boundaries, replying to threads, and resolving conversations.
Install
npx skillscat add straub/agent-skills/github-pr-review-comments Install via the SkillsCat registry.
GitHub PR Review Comments
This skill provides a comprehensive workflow for managing PR review comments using the gh CLI and GitHub's GraphQL API.
When to Use This Skill
Use this skill when:
- Asked to address PR review comments
- Finding unreplied or unresolved review threads
- Replying to review comments
- Resolving or unresolving review threads
- Working with review threads programmatically (coding agents, automation)
Finding All Review Comments (Reliable Method)
CRITICAL: PR review comments are paginated and span multiple review rounds. Simple queries will miss comments. Use this comparison approach:
Step 1: Get ALL Original Comment IDs
gh api repos/{owner}/{repo}/pulls/{pr}/comments --paginate \
--jq '[.[] | select(.in_reply_to_id == null)] | .[].id' \
| sort -n > /tmp/all_original_comments.txtThis finds all "top-level" review comments (not replies).
Step 2: Check Your Username
gh api user -q .loginStep 3: Get ALL Comment IDs That Have Your Replies
gh api repos/{owner}/{repo}/pulls/{pr}/comments --paginate \
--jq '[.[] | select(.user.login == "{your_username}" and .in_reply_to_id != null)] | .[].in_reply_to_id' \
| sort -n > /tmp/replied_comments.txtStep 4: Find Unreplied Comments (Set Difference)
comm -23 /tmp/all_original_comments.txt /tmp/replied_comments.txtThis shows comment IDs that don't have your reply yet.
Why Simple Approaches Fail
- Time-based filtering misses comments from earlier review rounds added to the same PR
- Non-paginated queries miss comments beyond the first page (typically 30-100 per page)
- Counting reviews vs comments is misleading - a single review can have many comments
- The PR "comments" field only shows issue-style comments, not review comments
Getting Comment Details
Once you have unreplied comment IDs, get full details:
gh api repos/{owner}/{repo}/pulls/{pr}/comments --paginate \
--jq '[.[] | select(.id == {id1} or .id == {id2})] | .[] | {id, path, line, body}'Replace {id1}, {id2} with actual comment IDs.
Replying to Review Comments
Basic Reply to a Comment
gh api repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id}/replies \
-X POST -f body='Your response here'Response Format Best Practices
- Start with status emoji:
- ✅ for addressed/fixed
- 🔄 for acknowledged/will fix
- 💭 for discussion/clarification needed
- Reference specific commits when fixes are made:
**FIXED** in commit abc1234 - Be concise but clear about what action was taken
- Group related fixes into single commits with descriptive messages
Example Responses
# Fixed issue
gh api repos/owner/repo/pulls/123/comments/456/replies \
-X POST -f body='✅ **FIXED** in commit abc1234 - Refactored to use async/await pattern'
# Acknowledged
gh api repos/owner/repo/pulls/123/comments/789/replies \
-X POST -f body='🔄 Good catch! Will fix in next commit'Working with Review Threads (GraphQL API)
The GitHub GraphQL API provides powerful capabilities for managing review threads.
Fetch Unresolved Review Threads
gh api graphql -f query='
{
repository(owner: "OWNER", name: "REPO") {
pullRequest(number: PR_NUMBER) {
reviewThreads(first: 100) {
nodes {
id
isResolved
comments(first: 100) {
nodes {
id
body
author { login }
path
line
}
}
}
}
}
}
}'Reply to a Review Thread
Use the thread ID from reviewThreads.nodes[].id (format: PRRT_kwDO...), NOT the comment ID:
gh api graphql -f query='
mutation {
addPullRequestReviewThreadReply(input: {
pullRequestReviewThreadId: "PRRT_kwDO..."
body: "Your reply here"
}) {
comment { id }
}
}'Resolve a Review Thread
gh api graphql -f query='
mutation {
resolveReviewThread(input: {threadId: "PRRT_kwDO..."}) {
thread { id isResolved }
}
}'Thread IDs are in the id field of each thread node (format: PRRT_kwDO...).
Unresolve a Review Thread
gh api graphql -f query='
mutation {
unresolveReviewThread(input: {threadId: "PRRT_kwDO..."}) {
thread { id isResolved }
}
}'Workflow: Addressing All Review Comments
- Find unreplied comments using the comparison method above
- Get comment details for each unreplied comment
- Make necessary code changes to address the feedback
- Commit changes with descriptive messages
- Reply to each comment with status and commit reference
- Resolve threads when appropriate (only after fix is confirmed and pushed)
Examples
Example 1: Find and List All Unreplied Comments for PR #42
# Set variables
OWNER="myorg"
REPO="myrepo"
PR="42"
# Step 1: Get all original comment IDs
gh api repos/$OWNER/$REPO/pulls/$PR/comments --paginate \
--jq '[.[] | select(.in_reply_to_id == null)] | .[].id' \
| sort -n > /tmp/all_original_comments.txt
# Step 2: Get your username
USERNAME=$(gh api user -q .login)
# Step 3: Get comment IDs you've replied to
gh api repos/$OWNER/$REPO/pulls/$PR/comments --paginate \
--jq "[.[] | select(.user.login == \"$USERNAME\" and .in_reply_to_id != null)] | .[].in_reply_to_id" \
| sort -n > /tmp/replied_comments.txt
# Step 4: Find unreplied comment IDs
UNREPLIED=$(comm -23 /tmp/all_original_comments.txt /tmp/replied_comments.txt)
# Step 5: Get details for unreplied comments
for id in $UNREPLIED; do
gh api repos/$OWNER/$REPO/pulls/$PR/comments --paginate \
--jq "[.[] | select(.id == $id)] | .[] | {id, path, line, body, author: .user.login}"
doneExample 2: Reply to Multiple Comments After Fix
OWNER="myorg"
REPO="myrepo"
PR="42"
COMMIT="abc1234"
# Reply to comment about async refactoring
gh api repos/$OWNER/$REPO/pulls/$PR/comments/111/replies \
-X POST -f body="✅ **FIXED** in commit $COMMIT - Refactored to use async/await"
# Reply to comment about error handling
gh api repos/$OWNER/$REPO/pulls/$PR/comments/222/replies \
-X POST -f body="✅ **FIXED** in commit $COMMIT - Added try/catch with proper error logging"Example 3: Get All Unresolved Threads and Reply
OWNER="myorg"
REPO="myrepo"
PR="42"
# Fetch unresolved threads
THREADS=$(gh api graphql -f query="
{
repository(owner: \"$OWNER\", name: \"$REPO\") {
pullRequest(number: $PR) {
reviewThreads(first: 100) {
nodes {
id
isResolved
comments(first: 1) {
nodes { body path }
}
}
}
}
}
}")
echo "$THREADS" | jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)'
# Reply to a specific thread and resolve it
THREAD_ID="PRRT_kwDOAbc123"
gh api graphql -f query="
mutation {
addPullRequestReviewThreadReply(input: {
pullRequestReviewThreadId: \"$THREAD_ID\"
body: \"✅ Fixed in latest commit\"
}) {
comment { id }
}
}"
gh api graphql -f query="
mutation {
resolveReviewThread(input: {threadId: \"$THREAD_ID\"}) {
thread { id isResolved }
}
}"Guidelines
- Always paginate when fetching PR comments - use
--paginateflag - Use set comparison (comm command) to reliably find unreplied comments
- Reply before resolving - add a comment explaining the fix before marking as resolved
- Reference commits in replies so reviewers can verify the fix
- Group related changes into single commits with clear messages
- Test your fixes before replying to ensure the issue is actually addressed
- Be specific in replies about what changed and why
- Keep replies concise but informative
- Use emojis consistently for status (✅ fixed, 🔄 in progress, 💭 discussion)
Notes
REST API vs GraphQL API
REST API (
gh api repos/.../pulls/.../comments):- Better for listing and filtering comments
- Simpler for replies
- Requires pagination for complete results
GraphQL API (
gh api graphql):- Better for fetching threads with nested structure
- Required for resolving/unresolving threads
- More efficient for complex queries (fewer API calls)
- Thread IDs have format
PRRT_kwDO...
Comment ID vs Thread ID
- Comment IDs are numeric (e.g.,
123456789) - Thread IDs are GraphQL node IDs (e.g.,
PRRT_kwDOAbc123...) - Use comment IDs for REST API replies
- Use thread IDs for GraphQL thread operations
Automation Considerations
For coding agents and automation:
- Store comment IDs and thread IDs for tracking
- Batch operations where possible to reduce API calls
- Add delays between API calls to respect rate limits
- Log all API responses for debugging
- Handle errors gracefully (comment might be deleted, PR might be closed)
Future Enhancement
When gh pr review-thread command becomes available (gh cli #12419), it will provide:
gh pr review-thread resolve <thread-id>gh pr review-thread unresolve <thread-id>gh pr review-thread list --unresolved
Until then, use the GraphQL mutations shown above.