| name | Using GitHub API with gh CLI |
| description | This skill teaches how to access GitHub's REST and GraphQL APIs via gh api for inline PR comments, review threads, and data not available through standard gh commands. Use this when you need PR review comments, thread status, or other GitHub data that gh pr view doesn't provide. |
| allowed-tools | ["Bash","Read","Write","Edit"] |
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 view doesn'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 status to 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
isResolved and isOutdated fields
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
gh api repos/OWNER/REPO/pulls/PR_NUMBER/reviews -q '.[].id'
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
isOutdated flags
- 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
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments --jq '.[] | "\(.id) \(.path):\(.line)"'
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:
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
}
}
}
}
}
}
}'
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:
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments/COMMENT_ID/replies \
-X POST -f body="Done. Created shared BigQuery bean."
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
PR_URL="https://github.com/owner/repo/pull/123"
OWNER=$(echo "$PR_URL" | cut -d'/' -f4)
REPO=$(echo "$PR_URL" | cut -d'/' -f5)
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
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
PR_URL="https://github.com/food-truck/mono/pull/123"
OWNER=$(echo "$PR_URL" | cut -d'/' -f4)
REPO=$(echo "$PR_URL" | cut -d'/' -f5)
PR_NUMBER=$(echo "$PR_URL" | cut -d'/' -f7)
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
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
PR_NUMBER="123"
OWNER="food-truck"
REPO="mono"
TOTAL=$(gh api repos/${OWNER}/${REPO}/pulls/${PR_NUMBER}/comments | jq '. | length')
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
gh auth status
Test API Access
gh api user -q '.login'
Verify Repository Access
gh api repos/OWNER/REPO -q '.name'
Troubleshooting
Issue: "Could not resolve to a Repository"
Cause: Incorrect owner/repo, or no access to repository
Solution:
gh repo view OWNER/REPO
gh api repos/OWNER/REPO -q '.name'
gh auth status
Issue: "Resource not accessible by integration"
Cause: Missing scopes in authentication token
Solution:
gh auth login --scopes repo,read:org
gh auth status
Issue: Empty results for inline comments
Cause: PR has no inline comments, or querying wrong endpoint
Solution:
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments
gh api repos/OWNER/REPO/issues/PR_NUMBER/comments
Issue: GraphQL query syntax error
Cause: Malformed GraphQL query
Solution:
gh api graphql -f query='
query {
viewer { login }
}'
Issue: Rate limiting
Cause: Too many API requests
Solution:
gh api rate_limit
Best Practices
-
Parse URLs with gh when possible
gh pr view URL --json is 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 status before debugging
- Saves time troubleshooting permission issues
-
Use -q flag with gh api
- Provides cleaner output
- Can extract specific fields directly
Quick Reference
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments --jq '.[] | "\(.id) \(.path):\(.line)"'
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments/COMMENT_ID/replies -X POST -f body="Done."
gh api graphql -f query='query { repository(owner:"OWNER", name:"REPO") { pullRequest(number:PR_NUMBER) { reviewThreads(first:10) { nodes { id isResolved } } } } }'
gh api graphql -f query='mutation { resolveReviewThread(input:{threadId:"PRRT_xxx"}) { thread { isResolved } } }'
gh api graphql -f query='mutation { unresolveReviewThread(input:{threadId:"PRRT_xxx"}) { thread { isResolved } } }'
gh pr view URL --json number,repository
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments | jq '. | length'
gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments | jq '.[] | select(.user.login == "username")'
gh api rate_limit
References
Related Skills
- Git Workflow - Understanding git operations for PR management
- JSON Processing with jq - Essential for parsing gh api output