| 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 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 IDpath- File pathline- Line numberbody- Comment textuser.login- Author usernamecreated_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 resolvedisOutdated- Whether the thread is outdated due to code changesdiffHunk- The code context for the commentpath- File pathbodyText- 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