(This is an English translation of my original Japanese article: 日本語版はこちら)
When using Claude Code or Claude Code Action to have AI autonomously review Pull Requests, I encountered several issues. To solve them, I created a Claude Skill specialized for PR review operations called github-pr-review-operation. Let me introduce it here.
The Problems
I faced these three issues:
- When adding inline comments, the AI would comment on the wrong line
- Claude Code Action comes with an MCP for adding inline comments, but it often placed comments about a specific line on a different line
- Even when instructed to "consider past comments on this PR," it couldn't properly retrieve the comment list
- It would fetch regular comments but proceed without getting inline comments
- It couldn't properly reply to comments on the PR
- It would reply to a specific comment by creating a separate inline comment instead
All these issues could be solved by properly using gh commands, but due to the probabilistic nature of LLMs, sometimes it worked and sometimes it didn't.
Teaching Claude Code the Operations with Claude Skills
As mentioned, the solution was to teach Claude Code how to use gh commands. Claude Skills seemed perfect for this purpose, so I created a custom Claude Skill specialized for PR review operations.
When creating Skills, it's good to use the Skill Generator that's built into Claude by default. I used the following prompt in Claude Desktop to create the base, then made various adjustments while testing it:
I want to create a skill for GitHub Pull Request reviews with the necessary operations. The required operations are:
- Get PR title and description
- Get PR file changes
- Get all comments on the PR
- Comment on the PR
- Add inline comment with line specification
- Reply to a specific comment on the PR
The final result was a very simple structure with just SKILL.md. Using this alone solved the above issues almost 100% of the time. Very convenient!
SKILL.md:
---
name: github-pr-review-operation
description: "Skill for GitHub Pull Request review operations. Executes PR info retrieval, diff viewing, comment fetching/posting, inline comments, and comment replies using gh commands. Use for PR reviews, code reviews, and PR operations."
---
# GitHub PR Review Operation
PR review operations using GitHub CLI (`gh`).
## Prerequisites
- `gh` installed
- Authenticated with `gh auth login`
## Parsing PR URL
Extract the following from PR URL `https://github.com/OWNER/REPO/pull/NUMBER`:
- `OWNER`: Repository owner
- `REPO`: Repository name
- `NUMBER`: PR number
## Operations
### 1. Get PR Information
```bash
gh pr view NUMBER --repo OWNER/REPO --json title,body,author,state,baseRefName,headRefName,url
```
### 2. Get Diff (with line numbers)
```bash
gh pr diff NUMBER --repo OWNER/REPO | awk '
/^@@/ {
match($0, /-([0-9]+)/, old)
match($0, /\+([0-9]+)/, new)
old_line = old[1]
new_line = new[1]
print $0
next
}
/^-/ { printf "L%-4d | %s\n", old_line++, $0; next }
/^\+/ { printf " R%-4d| %s\n", new_line++, $0; next }
/^ / { printf "L%-4d R%-4d| %s\n", old_line++, new_line++, $0; next }
{ print }
'
```
Output example:
```
@@ -46,15 +46,25 @@ jobs:
L46 R46 | prompt: |
L49 | - (deleted line)
R49 | + (added line)
L50 R50 | # Review guidelines
```
- `L number`: LEFT (base) side line number → Use with `side=LEFT` for inline comments
- `R number`: RIGHT (head) side line number → Use with `side=RIGHT` for inline comments
### 3. Get Comments
Issue Comments (comments on the entire PR):
```bash
gh api repos/OWNER/REPO/issues/NUMBER/comments --jq '.[] | {id, user: .user.login, created_at, body}'
```
Review Comments (comments on code lines):
```bash
gh api repos/OWNER/REPO/pulls/NUMBER/comments --jq '.[] | {id, user: .user.login, path, line, created_at, body, in_reply_to_id}'
```
### 4. Comment on PR
```bash
gh pr comment NUMBER --repo OWNER/REPO --body "Comment content"
```
### 5. Inline Comment (specifying code line)
First, get the head commit SHA:
```bash
gh api repos/OWNER/REPO/pulls/NUMBER --jq '.head.sha'
```
Single line comment:
```bash
gh api repos/OWNER/REPO/pulls/NUMBER/comments \
--method POST \
-f body="Comment content" \
-f commit_id="COMMIT_SHA" \
-f path="src/example.py" \
-F line=15 \
-f side=RIGHT
```
Multi-line comment (lines 10-15):
```bash
gh api repos/OWNER/REPO/pulls/NUMBER/comments \
--method POST \
-f body="Comment content" \
-f commit_id="COMMIT_SHA" \
-f path="src/example.py" \
-F line=15 \
-f side=RIGHT \
-F start_line=10 \
-f start_side=RIGHT
```
**Notes:**
- `-F` (uppercase): Use for numeric parameters (`line`, `start_line`). Using `-f` makes it a string and causes an error
- `side`: `RIGHT` (added lines) or `LEFT` (deleted lines)
### 6. Reply to Comment
```bash
gh api repos/OWNER/REPO/pulls/NUMBER/comments/COMMENT_ID/replies \
--method POST \
-f body="Reply content"
```
Use the `id` obtained from comment retrieval for `COMMENT_ID`.
Key Implementation Detail: Enabling Correct Line Placement for Inline Comments
Getting inline comments on the correct line was the most challenging part, requiring the most adjustments after auto-generating the Skills.
Let me explain why inline comments ended up on wrong lines. When you get a diff with the gh pr diff command, you get output in git diff format. For example, an excerpt from https://github.com/shibayu36/slack-explorer-mcp/pull/20 looks like this:
diff --git a/main.go b/main.go
index 3029a7a..9383209 100644
--- a/main.go
+++ b/main.go
@@ -3,6 +3,7 @@ package main
import (
"context"
"log/slog"
+ "net/http"
"os"
"github.com/mark3labs/mcp-go/mcp"
@@ -163,17 +164,55 @@ func main() {
handler.SearchUsersByName,
)
- if err := server.ServeStdio(s, server.WithStdioContextFunc(func(ctx context.Context) context.Context {
- ctx = WithSlackTokenFromEnv(ctx)
+ transport := os.Getenv("TRANSPORT")
+ if transport == "" {
+ transport = "stdio"
+ }
+
+ switch transport {
+ case "stdio":
+ if err := server.ServeStdio(s, server.WithStdioContextFunc(func(ctx context.Context) context.Context {
+ ctx = WithSlackTokenFromEnv(ctx)
- // Add session ID from ClientSession
- if session := server.ClientSessionFromContext(ctx); session != nil {
- ctx = WithSessionID(ctx, SessionID(session.SessionID()))
When adding inline comments, you need to specify a line number + side (LEFT for deleted lines or RIGHT for added lines):
gh api repos/OWNER/REPO/pulls/NUMBER/comments \
--method POST \
-f body="Comment content" \
-f commit_id="COMMIT_SHA" \
-f path="src/example.py" \
-F line=15 \
-f side=RIGHT
Because of these two API interfaces, to add an inline comment after getting the diff, you need to extract line numbers from lines like @@ -163,17 +164,55 @@ func main() {, consider the - or + at the beginning of lines, determine whether side should be LEFT (deleted) or RIGHT (added), and use that combination for commenting. This extraction operation is difficult for LLMs (even with Opus 4.5), which caused the issue of inline comments being placed on wrong lines.
However, thinking about it more, if you look at GitHub's web "Files changed" page, the information needed for inline comments is immediately visible:
So I thought, why not create the same information from the CLI? That's how I created the command in section "2. Get Diff (with line numbers)" of SKILL.md:
gh pr diff NUMBER --repo OWNER/REPO | awk '
/^@@/ {
match($0, /-([0-9]+)/, old)
match($0, /\+([0-9]+)/, new)
old_line = old[1]
new_line = new[1]
print $0
next
}
/^-/ { printf "L%-4d | %s\n", old_line++, $0; next }
/^\+/ { printf " R%-4d| %s\n", new_line++, $0; next }
/^ / { printf "L%-4d R%-4d| %s\n", old_line++, new_line++, $0; next }
{ print }
'
Using this to get the diff from https://github.com/shibayu36/slack-explorer-mcp/pull/20, you can get similar information to the web "Files changed" page in text format:
@@ -163,17 +164,55 @@ func main() {
L163 R164 | handler.SearchUsersByName,
L164 R165 | )
L165 R166 |
L166 | - if err := server.ServeStdio(s, server.WithStdioContextFunc(func(ctx context.Context) context.Context {
L167 | - ctx = WithSlackTokenFromEnv(ctx)
R167 | + transport := os.Getenv("TRANSPORT")
R168 | + if transport == "" {
R169 | + transport = "stdio"
R170 | + }
R171 | +
R172 | + switch transport {
R173 | + case "stdio":
R174 | + if err := server.ServeStdio(s, server.WithStdioContextFunc(func(ctx context.Context) context.Context {
R175 | + ctx = WithSlackTokenFromEnv(ctx)
L168 R176 |
L169 | - // Add session ID from ClientSession
L170 | - if session := server.ClientSessionFromContext(ctx); session != nil {
L171 | - ctx = WithSessionID(ctx, SessionID(session.SessionID()))
Now Claude Code can easily get the information needed for inline comments from the L163 R164 | format on the left, allowing it to place comments on the correct lines.
Conclusion
In this article, I introduced a Claude Skill that's useful when having AI autonomously review Pull Requests using Claude Code or Claude Code Action. You can use it just by placing the SKILL.md described above under .claude/skills/github-pr-review-operation/. Give it a try!

Top comments (0)