Initial commit
This commit is contained in:
500
commands/attach-review-to-pr.md
Normal file
500
commands/attach-review-to-pr.md
Normal file
@@ -0,0 +1,500 @@
|
||||
---
|
||||
description: Add line-specific review comments to pull requests using GitHub CLI API
|
||||
argument-hint: PR number or URL (optional - can work with current branch)
|
||||
---
|
||||
|
||||
# How to Attach Line-Specific Review Comments to Pull Requests
|
||||
|
||||
This guide explains how to add line-specific review comments to pull requests using the GitHub CLI (`gh`) API, similar to how the GitHub UI allows commenting on specific lines of code.
|
||||
|
||||
## Overview
|
||||
|
||||
While `gh pr review` provides basic review functionality (approve, request changes, general comments), it **does not support line-specific comments directly**. To add comments on specific lines of code, you must use the lower-level `gh api` command to call GitHub's REST API directly.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. GitHub CLI installed and authenticated:
|
||||
|
||||
```bash
|
||||
gh auth status
|
||||
```
|
||||
|
||||
2. Access to the repository and pull request you want to review
|
||||
|
||||
## Understanding GitHub's Review Comment System
|
||||
|
||||
GitHub has two types of PR comments:
|
||||
|
||||
1. **Issue Comments** - General comments on the PR conversation
|
||||
2. **Review Comments** - Line-specific comments on code changes
|
||||
|
||||
Review comments can be added in two ways:
|
||||
|
||||
- **Single comment** - Using the `/pulls/{pr}/comments` endpoint
|
||||
- **Review with multiple comments** - Using the `/pulls/{pr}/reviews` endpoint
|
||||
|
||||
## Adding a Single Line-Specific Comment
|
||||
|
||||
### Basic Syntax
|
||||
|
||||
```bash
|
||||
gh api repos/{owner}/{repo}/pulls/{pr_number}/comments \
|
||||
-f body='Your comment text here' \
|
||||
-f commit_id='<commit-sha>' \
|
||||
-f path='path/to/file.js' \
|
||||
-F line=42 \
|
||||
-f side='RIGHT'
|
||||
```
|
||||
|
||||
### Parameters Explained
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `body` | string | Yes | The text of the review comment (supports Markdown) |
|
||||
| `commit_id` | string | Yes | The SHA of the commit to comment on |
|
||||
| `path` | string | Yes | Relative path to the file being commented on |
|
||||
| `line` | integer | Yes | The line number in the diff (use `-F` for integers) |
|
||||
| `side` | string | Yes | `RIGHT` for new/modified lines, `LEFT` for deleted lines |
|
||||
| `start_line` | integer | No | For multi-line comments, the starting line |
|
||||
| `start_side` | string | No | For multi-line comments, the starting side |
|
||||
|
||||
### Parameter Flags
|
||||
|
||||
- `-f` (--field) - For string values
|
||||
- `-F` (--field) - For integer values (note the capital F)
|
||||
|
||||
### Complete Example
|
||||
|
||||
```bash
|
||||
# First, get the latest commit SHA for the PR
|
||||
gh api repos/NeoLabHQ/learning-platform-app/pulls/4 --jq '.head.sha'
|
||||
|
||||
# Then add your comment
|
||||
gh api repos/NeoLabHQ/learning-platform-app/pulls/4/comments \
|
||||
-f body='Consider adding error handling here. Should we confirm the lesson was successfully marked as completed before navigating away?' \
|
||||
-f commit_id='e152d0dd6cf498467eadbeb638bf05abe11c64d4' \
|
||||
-f path='src/components/LessonNavigationButtons.tsx' \
|
||||
-F line=26 \
|
||||
-f side='RIGHT'
|
||||
```
|
||||
|
||||
### Understanding Line Numbers
|
||||
|
||||
The `line` parameter refers to the **position in the diff**, not the absolute line number in the file:
|
||||
|
||||
- For **new files**: Line numbers match the file's line numbers
|
||||
- For **modified files**: Use the line number as it appears in the "Files changed" tab
|
||||
- For **multi-line comments**: Use `start_line` and `line` to specify the range
|
||||
|
||||
### Response
|
||||
|
||||
On success, returns a JSON object with comment details:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 2532291222,
|
||||
"pull_request_review_id": 3470545909,
|
||||
"path": "src/components/LessonNavigationButtons.tsx",
|
||||
"line": 26,
|
||||
"body": "Consider adding error handling here...",
|
||||
"html_url": "https://github.com/NeoLabHQ/learning-platform-app/pull/4#discussion_r2532291222",
|
||||
"created_at": "2025-11-16T22:40:46Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Adding Multiple Line-Specific Comments Together
|
||||
|
||||
To add multiple comments across different files in a single review, use the `/reviews` endpoint with JSON input.
|
||||
|
||||
### Why Use Reviews for Multiple Comments?
|
||||
|
||||
- **Atomic operation** - All comments are added together
|
||||
- **Single notification** - Doesn't spam with multiple notifications
|
||||
- **Better UX** - Appears as one cohesive review
|
||||
- **Same mechanism as GitHub UI** - "Start a review" → "Finish review"
|
||||
|
||||
### Basic Syntax
|
||||
|
||||
```bash
|
||||
cat <<'EOF' | gh api repos/{owner}/{repo}/pulls/{pr_number}/reviews --input -
|
||||
{
|
||||
"event": "COMMENT",
|
||||
"body": "Overall review summary (optional)",
|
||||
"comments": [
|
||||
{
|
||||
"path": "file1.tsx",
|
||||
"body": "Comment on file 1",
|
||||
"side": "RIGHT",
|
||||
"line": 15
|
||||
},
|
||||
{
|
||||
"path": "file2.tsx",
|
||||
"body": "Comment on file 2",
|
||||
"side": "RIGHT",
|
||||
"line": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### Review Event Types
|
||||
|
||||
| Event | Description | When to Use |
|
||||
|-------|-------------|-------------|
|
||||
| `COMMENT` | General review comment | Just leaving feedback without approval |
|
||||
| `APPROVE` | Approve the PR | Changes look good, ready to merge |
|
||||
| `REQUEST_CHANGES` | Request changes | Issues that must be fixed before merge |
|
||||
|
||||
### Complete Example
|
||||
|
||||
```bash
|
||||
cat <<'EOF' | gh api repos/NeoLabHQ/learning-platform-app/pulls/4/reviews --input -
|
||||
{
|
||||
"event": "COMMENT",
|
||||
"body": "Testing multiple line-specific comments via gh api",
|
||||
"comments": [
|
||||
{
|
||||
"path": "src/components/CourseCard.tsx",
|
||||
"body": "Test comment generated by Claude",
|
||||
"side": "RIGHT",
|
||||
"line": 15
|
||||
},
|
||||
{
|
||||
"path": "src/components/CourseProgressWidget.tsx",
|
||||
"body": "Test comment generated by Claude",
|
||||
"side": "RIGHT",
|
||||
"line": 30
|
||||
},
|
||||
{
|
||||
"path": "src/components/LessonProgressTracker.tsx",
|
||||
"body": "Test comment generated by Claude",
|
||||
"side": "RIGHT",
|
||||
"line": 20
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 3470546747,
|
||||
"state": "COMMENTED",
|
||||
"html_url": "https://github.com/NeoLabHQ/learning-platform-app/pull/4#pullrequestreview-3470546747",
|
||||
"submitted_at": "2025-11-16T22:42:43Z",
|
||||
"commit_id": "e152d0dd6cf498467eadbeb638bf05abe11c64d4"
|
||||
}
|
||||
```
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### Issue 1: "user_id can only have one pending review per pull request"
|
||||
|
||||
**Error Message:**
|
||||
|
||||
```
|
||||
gh: Validation Failed (HTTP 422)
|
||||
{"message":"Validation Failed","errors":[{"resource":"PullRequestReview","code":"custom","field":"user_id","message":"user_id can only have one pending review per pull request"}]}
|
||||
```
|
||||
|
||||
**Cause:** GitHub only allows one pending (unsubmitted) review per user per PR. If you previously started a review through the UI or API and didn't submit it, it blocks new review creation.
|
||||
|
||||
**Solution 1: Submit the pending review**
|
||||
|
||||
```bash
|
||||
# Check for pending reviews
|
||||
gh api repos/{owner}/{repo}/pulls/{pr_number}/reviews | jq '.[] | select(.state=="PENDING")'
|
||||
|
||||
# Submit it through the UI or ask the user to submit it
|
||||
```
|
||||
|
||||
**Solution 2: Use the single comment endpoint instead**
|
||||
|
||||
```bash
|
||||
# Add individual comments without creating a review
|
||||
gh api repos/{owner}/{repo}/pulls/{pr_number}/comments \
|
||||
-f body='Comment text' \
|
||||
-f commit_id='<sha>' \
|
||||
-f path='file.tsx' \
|
||||
-F line=26 \
|
||||
-f side='RIGHT'
|
||||
```
|
||||
|
||||
### Issue 2: Array syntax not working with --raw-field
|
||||
|
||||
**Failed Attempt:**
|
||||
|
||||
```bash
|
||||
# This does NOT work - GitHub API receives an object, not an array
|
||||
gh api repos/{owner}/{repo}/pulls/{pr}/reviews \
|
||||
--raw-field 'comments[0][path]=file1.tsx' \
|
||||
--raw-field 'comments[0][line]=15' \
|
||||
--raw-field 'comments[1][path]=file2.tsx' \
|
||||
--raw-field 'comments[1][line]=30'
|
||||
```
|
||||
|
||||
**Error:**
|
||||
|
||||
```
|
||||
Invalid request. For 'properties/comments', {"0" => {...}, "1" => {...}} is not an array.
|
||||
```
|
||||
|
||||
**Solution:** Use JSON input via heredoc:
|
||||
|
||||
```bash
|
||||
cat <<'EOF' | gh api repos/{owner}/{repo}/pulls/{pr}/reviews --input -
|
||||
{
|
||||
"comments": [...]
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### Issue 3: Invalid line number
|
||||
|
||||
**Error Message:**
|
||||
|
||||
```
|
||||
Pull request review thread line must be part of the diff
|
||||
```
|
||||
|
||||
**Cause:** The line number doesn't exist in the diff for this file.
|
||||
|
||||
**Solutions:**
|
||||
|
||||
- Verify the file was actually changed in this PR
|
||||
- Check the "Files changed" tab to see actual line numbers in the diff
|
||||
- Ensure you're using the correct `commit_id` (the latest commit in the PR)
|
||||
|
||||
### Issue 4: Wrong commit_id
|
||||
|
||||
**Error Message:**
|
||||
|
||||
```
|
||||
commit_sha is not part of the pull request
|
||||
```
|
||||
|
||||
**Solution:** Get the latest commit SHA:
|
||||
|
||||
```bash
|
||||
gh api repos/{owner}/{repo}/pulls/{pr_number} --jq '.head.sha'
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Get PR Information First
|
||||
|
||||
Before adding comments, gather necessary information:
|
||||
|
||||
```bash
|
||||
# Get PR details
|
||||
gh pr view {pr_number} --json headRefOid,files
|
||||
|
||||
# Or use the API
|
||||
gh api repos/{owner}/{repo}/pulls/{pr_number} --jq '{commit: .head.sha, files: [.changed_files]}'
|
||||
|
||||
# List files changed
|
||||
gh api repos/{owner}/{repo}/pulls/{pr_number}/files --jq '.[] | {filename: .filename, additions: .additions, deletions: .deletions}'
|
||||
```
|
||||
|
||||
### 2. Check for Pending Reviews
|
||||
|
||||
```bash
|
||||
# Check if you have a pending review
|
||||
gh api repos/{owner}/{repo}/pulls/{pr_number}/reviews \
|
||||
--jq '.[] | select(.state=="PENDING" and .user.login=="YOUR_USERNAME")'
|
||||
```
|
||||
|
||||
### 3. Use Meaningful Comment Text
|
||||
|
||||
- Be specific and constructive
|
||||
- Reference documentation or best practices
|
||||
- Suggest alternatives when requesting changes
|
||||
- Use code blocks for code suggestions:
|
||||
|
||||
```markdown
|
||||
Consider using async/await:
|
||||
|
||||
\`\`\`typescript
|
||||
async function getData() {
|
||||
const result = await fetch(url);
|
||||
return result.json();
|
||||
}
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
### 4. Batch Related Comments
|
||||
|
||||
Use the review endpoint to group related comments:
|
||||
|
||||
- All comments for a single file/area
|
||||
- All comments for a specific concern (security, performance, etc.)
|
||||
- Complete review session
|
||||
|
||||
### 5. Choose the Right Event Type
|
||||
|
||||
```bash
|
||||
# For feedback during development
|
||||
"event": "COMMENT"
|
||||
|
||||
# When approving
|
||||
"event": "APPROVE"
|
||||
|
||||
# When blocking merge
|
||||
"event": "REQUEST_CHANGES"
|
||||
```
|
||||
|
||||
## Workflow Examples
|
||||
|
||||
### Example 1: Quick Single Comment
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
OWNER="NeoLabHQ"
|
||||
REPO="learning-platform-app"
|
||||
PR=4
|
||||
|
||||
# Get latest commit
|
||||
COMMIT=$(gh api repos/$OWNER/$REPO/pulls/$PR --jq '.head.sha')
|
||||
|
||||
# Add comment
|
||||
gh api repos/$OWNER/$REPO/pulls/$PR/comments \
|
||||
-f body='Consider extracting this into a separate function for better testability' \
|
||||
-f commit_id="$COMMIT" \
|
||||
-f path='src/utils/validation.ts' \
|
||||
-F line=45 \
|
||||
-f side='RIGHT'
|
||||
```
|
||||
|
||||
### Example 2: Comprehensive Review
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
OWNER="NeoLabHQ"
|
||||
REPO="learning-platform-app"
|
||||
PR=4
|
||||
|
||||
# Create review with multiple comments
|
||||
cat <<EOF | gh api repos/$OWNER/$REPO/pulls/$PR/reviews --input -
|
||||
{
|
||||
"event": "COMMENT",
|
||||
"body": "Thanks for the PR! I've reviewed the changes and have a few suggestions.",
|
||||
"comments": [
|
||||
{
|
||||
"path": "src/components/CourseCard.tsx",
|
||||
"body": "Consider memoizing this component to prevent unnecessary re-renders",
|
||||
"side": "RIGHT",
|
||||
"line": 25
|
||||
},
|
||||
{
|
||||
"path": "src/utils/courseProgress.ts",
|
||||
"body": "This function could benefit from error handling for invalid course IDs",
|
||||
"side": "RIGHT",
|
||||
"line": 12
|
||||
},
|
||||
{
|
||||
"path": "src/state/CourseProgressState.ts",
|
||||
"body": "Consider adding JSDoc comments to document the expected behavior",
|
||||
"side": "RIGHT",
|
||||
"line": 8
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### Example 3: Multi-line Comment
|
||||
|
||||
```bash
|
||||
# Comment on a range of lines (e.g., lines 10-15)
|
||||
gh api repos/$OWNER/$REPO/pulls/$PR/comments \
|
||||
-f body='This entire block could be simplified using array destructuring' \
|
||||
-f commit_id="$COMMIT" \
|
||||
-f path='src/utils/parser.ts' \
|
||||
-F start_line=10 \
|
||||
-f start_side='RIGHT' \
|
||||
-F line=15 \
|
||||
-f side='RIGHT'
|
||||
```
|
||||
|
||||
## Helpful Helper Scripts
|
||||
|
||||
### Get PR Files and Lines
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# pr-files.sh - List all files changed in a PR with line counts
|
||||
|
||||
OWNER="$1"
|
||||
REPO="$2"
|
||||
PR="$3"
|
||||
|
||||
gh api repos/$OWNER/$REPO/pulls/$PR/files --jq '.[] | "\(.filename): +\(.additions)/-\(.deletions)"'
|
||||
```
|
||||
|
||||
### Check Review Status
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# check-reviews.sh - Check review status for a PR
|
||||
|
||||
OWNER="$1"
|
||||
REPO="$2"
|
||||
PR="$3"
|
||||
|
||||
echo "=== Reviews ==="
|
||||
gh api repos/$OWNER/$REPO/pulls/$PR/reviews --jq '.[] | "\(.user.login): \(.state) at \(.submitted_at)"'
|
||||
|
||||
echo -e "\n=== Pending Reviews ==="
|
||||
gh api repos/$OWNER/$REPO/pulls/$PR/reviews --jq '.[] | select(.state=="PENDING") | "\(.user.login): \(.state)"'
|
||||
```
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [GitHub API: Pull Request Review Comments](https://docs.github.com/rest/pulls/comments)
|
||||
- [GitHub API: Pull Request Reviews](https://docs.github.com/rest/pulls/reviews)
|
||||
- [GitHub CLI Manual](https://cli.github.com/manual/)
|
||||
- [Create PR Command](./create-pr.md)
|
||||
- [Commit Command](./commit.md)
|
||||
|
||||
## API Reference
|
||||
|
||||
### POST /repos/{owner}/{repo}/pulls/{pull_number}/comments
|
||||
|
||||
Creates a review comment on a specific line.
|
||||
|
||||
**Endpoint:** `https://api.github.com/repos/{owner}/{repo}/pulls/{pull_number}/comments`
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `body` (string, required): Comment text
|
||||
- `commit_id` (string, required): SHA of commit
|
||||
- `path` (string, required): Relative file path
|
||||
- `line` (integer, required): Line number in diff
|
||||
- `side` (string, required): "LEFT" or "RIGHT"
|
||||
- `start_line` (integer, optional): Start line for multi-line
|
||||
- `start_side` (string, optional): Start side for multi-line
|
||||
|
||||
### POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews
|
||||
|
||||
Creates a review with optional line-specific comments.
|
||||
|
||||
**Endpoint:** `https://api.github.com/repos/{owner}/{repo}/pulls/{pull_number}/reviews`
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `event` (string, required): "APPROVE", "REQUEST_CHANGES", or "COMMENT"
|
||||
- `body` (string, optional): Overall review comment
|
||||
- `comments` (array, optional): Array of comment objects
|
||||
- `commit_id` (string, optional): SHA of commit to review
|
||||
|
||||
**Comment Object:**
|
||||
|
||||
- `path` (string, required): Relative file path
|
||||
- `body` (string, required): Comment text
|
||||
- `line` (integer, required): Line number in diff
|
||||
- `side` (string, required): "LEFT" or "RIGHT"
|
||||
- `start_line` (integer, optional): Start line for multi-line
|
||||
- `start_side` (string, optional): Start side for multi-line
|
||||
Reference in New Issue
Block a user