Drive the GitHub CLI (gh) to review a pull request — fetch the diff and context, post inline review comments anchored to specific diff lines, open conceptual conversation threads, and submit a verdict (APPROVE / REQUEST_CHANGES / COMMENT). Use when reviewing or commenting on a GitHub PR. Triggers on: gh, github cli, pull request, pr review, post pr comment, gh api, review github pr.
Scanned 9/12/2026
Install to Claude Code
npx -y skills add igorilic/agentic-orchestration --skill gh-cli --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Gh Cli?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/igorilic-gh-cli)More formats (shields.io, HTML) on the badges page.
---
name: gh-cli
description: >
Drive the GitHub CLI (gh) to review a pull request — fetch the diff and
context, post inline review comments anchored to specific diff lines,
open conceptual conversation threads, and submit a verdict
(APPROVE / REQUEST_CHANGES / COMMENT). Use when reviewing or commenting
on a GitHub PR. Triggers on: gh, github cli, pull request, pr review,
post pr comment, gh api, review github pr.
---
## gh CLI — PR review & commenting
Everything here drives `gh` via the shell. `gh api repos/{owner}/{repo}/...`
auto-expands `{owner}`/`{repo}` from the current clone — in the URL **path**
and in `-F`/`--field` values (**not** `-f`/`--raw-field`, which sends the
literal string, and **not** inside an `--input` JSON body). Set
`GH_REPO=OWNER/REPO` or pass `-R OWNER/REPO` when outside the repo.
### 0. Auth check (do this first)
```bash
gh auth status # must be authenticated; token needs `repo` scope to post reviews
ME=$(gh api user -q .login) # your login — used for the self-PR fallback below
```
### 1. Resolve the target
```bash
PR=42
gh pr view "$PR" --json number,title,state,isDraft,author,headRefName,baseRefName,headRefOid,url,mergeable
HEAD_SHA=$(gh pr view "$PR" --json headRefOid -q .headRefOid) # commit_id for review comments
AUTHOR=$(gh pr view "$PR" --json author -q .author.login)
```
### 2. Fetch context (read-only)
```bash
gh pr diff "$PR" # unified diff (the change to review)
gh api --paginate repos/{owner}/{repo}/pulls/$PR/files # per-file patch hunks — validate comment line numbers against these
gh pr checks "$PR" # CI status (evidence, not a finding to re-derive)
# Existing feedback — read before posting so you never duplicate a point:
gh api --paginate repos/{owner}/{repo}/pulls/$PR/comments # inline review comments
gh api --paginate repos/{owner}/{repo}/pulls/$PR/reviews # prior reviews
gh api --paginate repos/{owner}/{repo}/issues/$PR/comments # conversation comments
# Linked issue (acceptance criteria) — PRs link via "Closes #N":
gh pr view "$PR" --json body,closingIssuesReferences
gh issue view <N> --json number,title,body,state,labels
```
### 3. Post inline comments + verdict in ONE review (the reliable path)
Bundle every line-anchored comment and the verdict into a single
`POST .../pulls/<PR>/reviews` call. The nested `comments[]` array can't be
built with `-f`/`-F`, so pass raw JSON. Build it with `jq` (safest — it
escapes bodies for you) **or** a quoted heredoc, then `--input`.
Write the static `comments[]` array with a **quoted** heredoc (so backticks
and `$` in bodies stay literal), then let `jq` inject the **computed**
`commit_id` and `event` — never hardcode the verdict, or you'll post
`REQUEST_CHANGES` on a self-PR (422). `$EVENT` and `$HEAD_SHA` come from
steps 1 and 5; on your own PR `$EVENT` is `COMMENT`.
```bash
cat > /tmp/comments.json <<'JSON'
[
{ "path": "src/auth.ts", "line": 88, "side": "RIGHT",
"body": "**🔴 CRITICAL — SQL injection.** userId is concatenated into the query; use a bound parameter (see suggestion)." },
{ "path": "src/api.ts", "start_line": 20, "start_side": "RIGHT", "line": 23, "side": "RIGHT",
"body": "**🟠 MAJOR — unhandled rejection.** Wrap this block in try/catch and return a 5xx." },
{ "path": "src/old.ts", "line": 7, "side": "LEFT",
"body": "**🟡 MINOR** — comment on a *deleted* line (LEFT = original file)." }
]
JSON
jq -n \
--arg sha "$HEAD_SHA" \
--arg event "$EVENT" \
--arg body "## diff-reviewer verdict: $EVENT — see inline comments + threads." \
--slurpfile comments /tmp/comments.json \
'{commit_id:$sha, event:$event, body:$body, comments:$comments[0]}' \
| gh api repos/{owner}/{repo}/pulls/$PR/reviews --input -
```
> `jq` escapes the bodies and injects the real `$HEAD_SHA`/`$EVENT`, so the
> verdict always matches step 5 (and the self-PR fallback). Keep `{owner}`/
> `{repo}` in the URL — they don't expand inside the JSON.
**Line/side rules (getting these wrong → HTTP 422):**
- `path` — repo-relative file path.
- `line` — the line number **in the file** the comment targets; for a
multi-line comment it is the **last** line of the range.
- `side` — `RIGHT` = added/unchanged (new file; the default), `LEFT` =
deleted (original file). Use `LEFT` for removed lines.
- Multi-line comment → **also** set `start_line` + `start_side` (the first
line/side; `start_line < line`). Single-line → omit both.
- The target line **must be part of the diff** (an added/changed/context
line inside a hunk) — validate against `pulls/$PR/files` patch hunks
first, or the whole call 422s.
- `event`: `APPROVE` | `REQUEST_CHANGES` | `COMMENT`. `body` is **required**
for `REQUEST_CHANGES`/`COMMENT`. Omitting `event` leaves the review
**PENDING** (unsubmitted, no notification) — don't forget to submit.
**Proposed fixes — suggestion blocks.** When the fix is a local edit,
include a suggestion block in the comment `body` so the author applies it in
one click. The body is a JSON string, so the block is a `\n`-delimited
fenced section like this (literal newlines shown for clarity):
````text
**🔴 CRITICAL — SQL injection.** Use a parameterised query:
```suggestion
const rows = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
```
````
In JSON that body becomes:
`"...Use a parameterised query:\n\n```suggestion\n const rows = ...\n```"`
(the suggestion replaces exactly the commented line(s)).
### 4. Post a conceptual (non-line) thread
For findings not tied to one line — architecture, missing tests, PR scope:
```bash
gh pr comment "$PR" --body "**🟠 MAJOR — no tests for the new auth path.** AC-3 requires a failing-login test; none added. ..."
```
### 5. Verdict & self-PR fallback
The `event` on the review **is** the verdict. But you **cannot** `APPROVE`
or `REQUEST_CHANGES` your **own** PR (GitHub returns 422). Branch on it:
```bash
if [ "$ME" = "$AUTHOR" ]; then
EVENT="COMMENT" # always allowed on your own PR; still carries inline comments + summary
else
EVENT="REQUEST_CHANGES" # or APPROVE / COMMENT per the findings
fi
```
- 🔴 present → `REQUEST_CHANGES` (or `COMMENT` if self-PR).
- only 🟡/🟢 → `COMMENT`.
- clean / nits only → `APPROVE` (non-author).
### Gotchas
- Prefer the **reviews** endpoint over `POST .../pulls/$PR/comments` — the
standalone comments endpoint frequently 422s even with valid line/side.
- `{owner}`/`{repo}` expand in the URL path and in `-F`/`--field` values
(**not** `-f`/`--raw-field`, which is literal), and **not** inside `--input`
JSON — keep them in the path.
- `position` (diff-offset anchoring) is **deprecated** — always use `line`/`side`.
- Pass `commit_id = $HEAD_SHA`; a stale SHA marks comments "outdated".
- Throttle bulk posts — review creation sends notifications and can hit a
secondary rate limit.
- Never echo the token; `repo` scope is required to post (read-only → 403).
- **De-dup across runs:** comments are not idempotent — a second run re-posts
everything. Stamp each comment body with a `<!-- diff-reviewer -->` marker,
and in step 2 skip any finding whose file+line already carries that marker in
the fetched `pulls/$PR/comments`.
- **Secret in the diff?** Redact the value in the comment (`AKIA****`) and do
**not** put the secret in a ```` ```suggestion ```` block — that would
republish it. Recommend rotation + history removal.
- **Staged / silent option:** omit `event` to create the review **PENDING**
(no notification). You can show the human the rendered review on GitHub, then
submit it via `POST .../pulls/$PR/reviews/$REVIEW_ID/events` with the chosen
event — a belt-and-suspenders complement to the in-chat preview gate.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!