Using GitHub API with gh CLI
The gh CLI doesn't have first-class support for inline/in-code pull request review comments (the line/thread comments on the "Files changed" tab). This skill shows how to access these and other GitHub data programmatically using gh api.
What This Skill Does
- •Access inline PR review comments
- •Get review thread status (resolved/unresolved)
- •Reply to inline review comments
- •Resolve and unresolve review threads
- •Use GitHub's REST API via gh api
- •Use GitHub's GraphQL API via gh api
- •Extract owner/repo/PR number from URLs
- •Filter and query PR review data
When You Need This Skill
Use this skill when:
- •Standard
gh pr viewdoesn't show the data you need - •You need inline/line-specific review comments from "Files changed" tab
- •You need to check if review threads are resolved/unresolved
- •You need to programmatically process review data
For day-to-day gh usage (viewing PRs, creating PRs, finding branch names), see the gh CLI section in CLAUDE.md.
Prerequisites
- •gh CLI installed - Already available as
gh - •Authenticated with GitHub - Run
gh auth statusto verify - •jq - For parsing JSON output (optional but recommended)
Core Concepts
What gh Commands Show vs Don't Show
gh pr view shows:
- •PR conversation comments
- •Review summaries
- •Overall PR metadata
gh pr view does NOT show:
- •Inline review comments (line-specific comments)
- •Review thread resolved/unresolved status
- •Code diff context for comments
Use gh api when you need the data that gh pr view doesn't provide.
REST API vs GraphQL API
REST API:
- •Simple for basic inline comment retrieval
- •Returns arrays of comment objects
- •Good for straightforward queries
GraphQL API:
- •Better for complex queries
- •Returns thread structure with resolved status
- •Can fetch everything in one query
- •Provides
isResolvedandisOutdatedfields
REST API Usage
Get All Inline Review Comments for a PR
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments \
-q '.[] | {id, path, line, body: .body, user: .user.login, created_at}'
Endpoint: GET /repos/{owner}/{repo}/pulls/{pull_number}/comments
Returns: Array of comment objects with:
- •
id- Comment ID - •
path- File path - •
line- Line number - •
body- Comment text - •
user.login- Author username - •
created_at- Timestamp
Reference: REST API endpoints for pull request review comments
Get Inline Comments for a Specific Review
# First get reviews for the PR to find REVIEW_ID
gh api repos/OWNER/REPO/pulls/PR_NUMBER/reviews -q '.[].id'
# Then list the inline comments for one review
gh api repos/OWNER/REPO/pulls/PR_NUMBER/reviews/REVIEW_ID/comments \
-q '.[] | {id, path, line, body: .body, author: .user.login}'
Endpoints:
- •
GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews(list reviews) - •
GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/comments(list comments)
Reference: REST API endpoints for pull request reviews
Get PR Conversation Comments (Non-Inline)
gh api repos/OWNER/REPO/issues/PR_NUMBER/comments \
-q '.[] | {id, body: .body, author: .user.login}'
Note: PR conversation comments use the Issues API endpoint, not Pulls.
Endpoint: GET /repos/{owner}/{repo}/issues/{issue_number}/comments
Reference: REST API endpoints for issue comments
GraphQL API Usage
Get All Review Threads with Resolved Status
gh api graphql -f query='
query($owner:String!, $repo:String!, $number:Int!, $n:Int=100) {
repository(owner:$owner, name:$repo) {
pullRequest(number:$number) {
reviewThreads(first:$n) {
nodes {
id
isResolved
isOutdated
comments(first:$n) {
nodes {
databaseId
bodyText
path
diffHunk
author { login }
createdAt
url
}
}
}
}
}
}
}' -F owner=OWNER -F repo=REPO -F number=PR_NUMBER
Key Fields:
- •
isResolved- Whether the thread is marked as resolved - •
isOutdated- Whether the thread is outdated due to code changes - •
diffHunk- The code context for the comment - •
path- File path - •
bodyText- Comment content
Reference: GraphQL API documentation
When to Use REST vs GraphQL
Use REST when:
- •You just need inline comments (body, path/line, author, timestamps)
- •You want simplicity and speed
- •You're fetching data from a single PR
- •You don't need resolved/unresolved status
Use GraphQL when:
- •You need thread structure and resolved/unresolved status
- •You need
isOutdatedflags - •You want to fetch everything in one query
- •REST doesn't expose the metadata you need
- •You're building complex queries across related data
Responding to Review Comments
Reply to an Inline Comment
# Get comment IDs first gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments --jq '.[] | "\(.id) \(.path):\(.line)"' # Reply to a specific comment gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments/COMMENT_ID/replies \ -X POST -f body="Done. Fixed in latest commit."
Endpoint: POST /repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies
Reference: Create a reply for a review comment
Resolve a Review Thread
Review threads can only be resolved via GraphQL. First get the thread ID, then resolve it:
# Get thread IDs (note: these start with PRRT_)
gh api graphql -f query='
query {
repository(owner: "OWNER", name: "REPO") {
pullRequest(number: PR_NUMBER) {
reviewThreads(first: 10) {
nodes {
id
isResolved
comments(first: 1) {
nodes {
body
path
}
}
}
}
}
}
}'
# Resolve the thread
gh api graphql -f query='
mutation {
resolveReviewThread(input: {threadId: "PRRT_kwDOxxxxxx"}) {
thread {
isResolved
}
}
}'
Note: The threadId is the GraphQL node ID (starts with PRRT_), not the REST API comment ID.
Unresolve a Review Thread
gh api graphql -f query='
mutation {
unresolveReviewThread(input: {threadId: "PRRT_kwDOxxxxxx"}) {
thread {
isResolved
}
}
}'
Reply and Resolve in Sequence
A common workflow is to reply to feedback explaining what you did, then resolve the thread:
# 1. Reply to the comment (REST)
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments/COMMENT_ID/replies \
-X POST -f body="Done. Created shared BigQuery bean."
# 2. Resolve the thread (GraphQL)
gh api graphql -f query='
mutation {
resolveReviewThread(input: {threadId: "PRRT_kwDOxxxxxx"}) {
thread { isResolved }
}
}'
Common Use Cases
List All Unresolved Review Threads
gh api graphql -f query='
query($owner:String!, $repo:String!, $number:Int!) {
repository(owner:$owner, name:$repo) {
pullRequest(number:$number) {
reviewThreads(first:100) {
nodes {
isResolved
comments(first:1) {
nodes {
path
bodyText
author { login }
}
}
}
}
}
}
}' -F owner=OWNER -F repo=REPO -F number=PR_NUMBER \
| jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)'
Get All Comments by a Specific Reviewer
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments \
| jq '.[] | select(.user.login == "username") | {path, line, body: .body}'
Count Total Inline Comments
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments | jq '. | length'
Get Comments with Specific Text
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments \
| jq '.[] | select(.body | contains("TODO")) | {path, line, body: .body}'
Extracting URL Components
From PR URL
# From: https://github.com/owner/repo/pull/123 PR_URL="https://github.com/owner/repo/pull/123" # Extract owner OWNER=$(echo "$PR_URL" | cut -d'/' -f4) # Extract repo REPO=$(echo "$PR_URL" | cut -d'/' -f5) # Extract PR number PR_NUMBER=$(echo "$PR_URL" | cut -d'/' -f7)
Using gh to Parse URL
gh pr view "$PR_URL" --json number,repository \
-q '{number: .number, owner: .repository.owner.login, repo: .repository.name}'
This is more robust as it handles different URL formats.
Examples
Example 1: Get All Inline Comments for a PR
# User: "Show me all inline review comments on PR #123"
OWNER="food-truck"
REPO="mono"
PR_NUMBER="123"
gh api repos/${OWNER}/${REPO}/pulls/${PR_NUMBER}/comments \
-q '.[] | "[\(.path):\(.line)] @\(.user.login): \(.body)"'
Example 2: Find Unresolved Comments
# User: "What review comments are still unresolved on that PR?"
PR_URL="https://github.com/food-truck/mono/pull/123"
# Parse URL
OWNER=$(echo "$PR_URL" | cut -d'/' -f4)
REPO=$(echo "$PR_URL" | cut -d'/' -f5)
PR_NUMBER=$(echo "$PR_URL" | cut -d'/' -f7)
# Get unresolved threads
gh api graphql -f query='
query($owner:String!, $repo:String!, $number:Int!) {
repository(owner:$owner, name:$repo) {
pullRequest(number:$number) {
reviewThreads(first:100) {
nodes {
isResolved
comments(first:1) {
nodes {
path
bodyText
author { login }
}
}
}
}
}
}
}' -F owner=${OWNER} -F repo=${REPO} -F number=${PR_NUMBER} \
| jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false) | .comments.nodes[0] | "[\(.path)] @\(.author.login): \(.bodyText)"'
Example 3: Get My Comments on a PR
# User: "What comments did I leave on PR #123?"
MY_USERNAME=$(gh api user -q '.login')
gh api repos/food-truck/mono/pulls/123/comments \
| jq --arg user "$MY_USERNAME" '.[] | select(.user.login == $user) | {path, line, body: .body}'
Example 4: Summary of Review Activity
# User: "Give me a summary of review activity on PR #123"
PR_NUMBER="123"
OWNER="food-truck"
REPO="mono"
# Total comments
TOTAL=$(gh api repos/${OWNER}/${REPO}/pulls/${PR_NUMBER}/comments | jq '. | length')
# Comments by reviewer
echo "Total inline comments: $TOTAL"
echo ""
echo "By reviewer:"
gh api repos/${OWNER}/${REPO}/pulls/${PR_NUMBER}/comments \
| jq -r '.[] | .user.login' \
| sort | uniq -c | sort -rn
Verification
Check gh Authentication
# Verify you're authenticated gh auth status # Expected output shows authenticated user and scopes
Test API Access
# Test REST API access gh api user -q '.login' # Should return your GitHub username
Verify Repository Access
# Check if you can access a repo gh api repos/OWNER/REPO -q '.name' # Should return the repo name
Troubleshooting
Issue: "Could not resolve to a Repository"
Cause: Incorrect owner/repo, or no access to repository
Solution:
# Verify owner and repo names gh repo view OWNER/REPO # Check if you have access gh api repos/OWNER/REPO -q '.name' # Verify authentication gh auth status
Issue: "Resource not accessible by integration"
Cause: Missing scopes in authentication token
Solution:
# Re-authenticate with required scopes gh auth login --scopes repo,read:org # Check current scopes gh auth status
Issue: Empty results for inline comments
Cause: PR has no inline comments, or querying wrong endpoint
Solution:
# Verify PR has inline comments by viewing in browser # Check you're using pulls endpoint, not issues gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments # For conversation comments, use issues endpoint gh api repos/OWNER/REPO/issues/PR_NUMBER/comments
Issue: GraphQL query syntax error
Cause: Malformed GraphQL query
Solution:
# Test query structure with simpler query first
gh api graphql -f query='
query {
viewer { login }
}'
# Add complexity incrementally
# Check GitHub GraphQL Explorer for query validation
Issue: Rate limiting
Cause: Too many API requests
Solution:
# Check rate limit status gh api rate_limit # Wait for rate limit reset # Or use GraphQL (higher rate limits)
Best Practices
- •
Parse URLs with gh when possible
- •
gh pr view URL --jsonis more robust than string parsing - •Handles different URL formats automatically
- •
- •
Use GraphQL for complex queries
- •Single query vs multiple REST calls
- •Better for resolved/unresolved status
- •Higher rate limits
- •
Cache results locally
- •API calls count against rate limits
- •Save results to file for repeated analysis
- •
Use jq for filtering
- •Filter on client side to reduce API calls
- •Build up complex jq queries incrementally
- •
Check authentication first
- •Always verify
gh auth statusbefore debugging - •Saves time troubleshooting permission issues
- •Always verify
- •
Use -q flag with gh api
- •Provides cleaner output
- •Can extract specific fields directly
Quick Reference
# Get inline PR comments (REST)
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments
# Get comment IDs with file/line info
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments --jq '.[] | "\(.id) \(.path):\(.line)"'
# Reply to a comment (REST)
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments/COMMENT_ID/replies -X POST -f body="Done."
# Get thread IDs and resolved status (GraphQL)
gh api graphql -f query='query { repository(owner:"OWNER", name:"REPO") { pullRequest(number:PR_NUMBER) { reviewThreads(first:10) { nodes { id isResolved } } } } }'
# Resolve a thread (GraphQL)
gh api graphql -f query='mutation { resolveReviewThread(input:{threadId:"PRRT_xxx"}) { thread { isResolved } } }'
# Unresolve a thread (GraphQL)
gh api graphql -f query='mutation { unresolveReviewThread(input:{threadId:"PRRT_xxx"}) { thread { isResolved } } }'
# Parse PR URL
gh pr view URL --json number,repository
# Count comments
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments | jq '. | length'
# Filter by author
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments | jq '.[] | select(.user.login == "username")'
# Check rate limits
gh api rate_limit
References
- •GitHub CLI Manual
- •GitHub REST API Quickstart
- •GitHub GraphQL API Documentation
- •gh cli Discussion #3993 - Community discussion on retrieving PR reviews
Related Skills
- •Git Workflow - Understanding git operations for PR management
- •JSON Processing with jq - Essential for parsing gh api output