GitHub PR Draft (Pending) Review Comments
Posts review comments to a PR as pending drafts that remain invisible to everyone but the author until they submit the review via the GitHub UI (or another tool call). Useful for AI-generated code-review findings that the user wants to triage before publishing.
This is the only way to comment on a PR here
NEVER post a plain, immediately-visible comment on a pull request — not when autofixing CI, not when addressing review feedback, not even when replying to an existing review comment. Every comment on a PR goes through this skill as a pending review comment, and you never submit the pending review yourself — the author publishes it. That means:
- Do NOT use
add_issue_comment(on a PR),add_reply_to_pull_request_comment,gh pr comment, orgh pr review— they all publish on the spot. - Do NOT call
pull_request_review_writewithmethod: submit_pending(or thesubmitPullRequestReviewGraphQL mutation) unless the author explicitly asks you to publish. - A status update or "here's why CI is red" note is still a PR comment: post it as a pending comment (anchored to a relevant file/line, or a FILE-level comment), not a plain conversation comment.
Claude Code Cloud (GitHub MCP tools)
In the cloud setup gh is not authenticated — use the GitHub MCP tools. The GraphQL sections below are the local fallback.
-
Ensure a pending review exists. Each user has at most one pending review per PR. Create one with
pull_request_review_writeusingmethod: "create"and noevent(omittingeventis what makes it pending rather than submitted):pull_request_review_write(method: "create", owner, repo, pullNumber) -
Add each draft comment to that pending review with
add_comment_to_pending_review:add_comment_to_pending_review(owner, repo, pullNumber, path, body, subjectType: "LINE", line, side: "RIGHT")Use
subjectType: "FILE"(omitline/side) for a file-level note. AnchorLINEcomments to a line inside a diff hunk (see the gotcha below). -
Do NOT submit. Leave the review pending. Do not call
pull_request_review_writewithmethod: "submit_pending".
add_comment_to_pending_review has no reply field, so for replies to an existing review comment see the next section.
Replying to an existing review comment (pending)
A reply to a review comment must also be a pending draft — never a published reply. add_reply_to_pull_request_comment publishes immediately, so do not use it.
-
True threaded pending reply (gh/GraphQL): attach the reply to your pending review with
addPullRequestReviewThreadReply, passing the pending review'spullRequestReviewIdand the comment you're replying to asinReplyTo. Because it belongs to the pending review, it stays a draft until the review is submitted.gh api graphql -f query='mutation($rid:ID!,$cid:ID!,$body:String!) { addPullRequestReviewThreadReply(input: {pullRequestReviewId:$rid, inReplyTo:$cid, body:$body}) { comment { id } } }' -f rid="PRR_..." -f cid="PRRC_..." -f body="Reply text."cidis the target review comment's node ID (PRRC_...), andridis the pending review's ID (PRR_...) — create one first if none exists (see the workflow below). -
MCP-only fallback (Claude Code Cloud):
add_comment_to_pending_reviewcannot thread onto an existing comment. Instead add a pending inline comment at the same file and line as the original review comment, quoting or referencing it in the body (e.g. "Re your comment on this line: …"). It won't render as a threaded reply, but it stays pending and lands next to the original.
Key concepts
- A pending review is a personal draft review attached to a PR. Each authenticated user has at most one pending review per PR.
- Inline comments live inside a pending review until the user submits it. They are not visible to other collaborators while pending.
gh pr reviewandgh pr commentdo not create pending comments — they publish immediately. You must use the GraphQL API.
Workflow
1. Find the PR and any existing pending review
gh pr view --json number,url,headRefName
gh pr view <number> --json reviews
In the reviews array, look for the entry where author.login matches the current gh api user --jq .login AND state == "PENDING". Save its id (looks like PRR_kwDO...) — call it REVIEW_ID.
If no pending review exists, you'll need the PR's node ID instead:
gh api graphql -f query='query { repository(owner:"OWNER", name:"REPO") { pullRequest(number: N) { id } } }'
2. Verify the comment anchor line is inside a diff hunk
Critical gotcha: addPullRequestReviewThread returns { thread: null } with no error if the target line is not inside any added/changed hunk of the PR. Always anchor comments to lines that appear in the diff.
git diff <base>...HEAD -- <path> | grep '^@@'
Each @@ -old,oldN +new,newN @@ header tells you the new-file line range [new, new+newN-1] is in a hunk. If the line you want to comment on falls outside every hunk, pick the nearest in-hunk line and reference the off-hunk line in the comment body.
For findings on context lines that the diff didn't touch but a hunk surrounds (e.g. a function that the PR re-exposes a bug in), use the closest in-hunk line and explain the connection in the body.
3. Post the comment via GraphQL
Use a JSON file rather than -f/-F flags so multi-line bodies with backticks, quotes, and code fences round-trip correctly:
cat > /tmp/draft_comment.json <<'EOF'
{
"query": "mutation($input: AddPullRequestReviewThreadInput!) { addPullRequestReviewThread(input: $input) { thread { id line path } } }",
"variables": {
"input": {
"pullRequestReviewId": "PRR_kwDO...",
"path": "path/to/file.rs",
"line": 123,
"side": "RIGHT",
"body": "Comment body with `code` and ```fences``` and \"quotes\"."
}
}
}
EOF
gh api graphql --input /tmp/draft_comment.json
Required fields:
pullRequestReviewId— the existing pending review's ID. Or usepullRequestId(the PR node ID) instead — that creates a new pending review on the fly with this comment as its first entry.path— file path from repo root.line— 1-based line number in the new (right-side) file.side—"RIGHT"for new-file line,"LEFT"for old-file line. Default is"RIGHT"; include it explicitly for clarity.body— the comment markdown.
Optional fields for multi-line comments:
startLine— 1-based start line for a range.startSide—"RIGHT"or"LEFT".subjectType—"LINE"(default) or"FILE"for file-level comments.
A successful response returns { thread: { id: "PRRT_...", line, path } }. Save the thread ID if you might need to edit or delete the comment.
4. Verify the comments are pending, not published
gh api graphql -f query='query { node(id: "PRR_kwDO...") { ... on PullRequestReview { state comments(first: 20) { totalCount nodes { path line } } } } }'
state should still be "PENDING". The user submits via the GitHub UI (Files Changed → Finish your review) or with the submitPullRequestReview mutation if asked.
Editing or deleting a pending draft
Get the comment ID (not the thread ID — they're different):
gh api graphql -f query='query { node(id: "PRRT_THREAD_ID") { ... on PullRequestReviewThread { comments(first: 5) { nodes { id body } } } } }'
Edit:
gh api graphql -f query='mutation { updatePullRequestReviewComment(input: {pullRequestReviewCommentId: "PRRC_...", body: "new body"}) { pullRequestReviewComment { id } } }'
Delete:
gh api graphql -f query='mutation { deletePullRequestReviewComment(input: {id: "PRRC_..."}) { pullRequestReview { id } } }'
The thread is removed when its last comment is deleted.
Common pitfalls
- Silent
thread: null— Line is outside every diff hunk. Anchor to an in-hunk line; reference the real location in the body if needed. "Pull request review thread position is invalid"— You used the olderaddPullRequestReviewCommentmutation, which wants a 1-based diff position (not file line) and is more painful to compute. PreferaddPullRequestReviewThread.- Two pending reviews — You can't have two pending reviews on the same PR. If one exists, add to it via
pullRequestReviewId; don't passpullRequestId. - Comment ID vs. thread ID —
PRRT_...is a thread;PRRC_...is a comment. Delete/update mutations wantPRRC_.... - These publish immediately — never use them for a comment that should be pending:
gh pr review/gh pr comment, and the MCP toolsadd_issue_commentandadd_reply_to_pull_request_comment. A reply routed throughadd_reply_to_pull_request_commentis public the instant it posts. - Escaping in
-f query='...'— For mutations with complex bodies, prefer the--input file.jsonform. Single quotes around backticks and code fences fight shell quoting.
Quick reference
| Need | Mutation |
|------|----------|
| Add draft comment to existing pending review | addPullRequestReviewThread with pullRequestReviewId |
| Start a new pending review with first draft comment | addPullRequestReviewThread with pullRequestId |
| Reply to an existing review comment, as a draft | addPullRequestReviewThreadReply with pullRequestReviewId + inReplyTo |
| Edit a draft comment | updatePullRequestReviewComment with pullRequestReviewCommentId |
| Delete a draft comment | deletePullRequestReviewComment with comment id |
| Submit (publish) the pending review | submitPullRequestReview with event: COMMENT / APPROVE / REQUEST_CHANGES |
| Discard pending review entirely | deletePullRequestReview with pullRequestReviewId |