Skip to main content

GitHub Action that Reviews Every PR

Advanced
What you'll learn
  • Wire Claude Code headless into a pull_request workflow that reviews the diff and comments
  • Set the permissions block to comment-only, and say why contents: write is the line you do not cross
  • Treat pull request content as untrusted input, because that is exactly what it is
  • Keep the cost of a review bounded as diffs grow
  • Write the prompt so the comment gets read instead of collapsed

A classic high-value automation: have Claude review every pull request and post its findings as a comment — running headless in CI. Here's the shape, with the guardrails that keep it safe.

What it does​

On each PR: check out the diff, ask Claude to review it for bugs/edge-cases/convention issues, and post a comment. Humans still decide; Claude just gives a fast first pass.

Guided walkthrough1 of 4
  1. A shallow clone cannot see the base branch, so the diff comes out empty and the review reads as a confident sign-off on nothing. Fetch full depth.

The workflow (sketch)​

name: Claude PR review
on: pull_request
permissions:
contents: read
pull-requests: write # to comment — NOT write to code
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Review the diff
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
git diff origin/${{ github.base_ref }}...HEAD > /tmp/diff.patch
claude -p "Review this diff for correctness bugs, missing edge cases, and
security issues. Report ONLY high-confidence findings as a Markdown
checklist with file:line. Diff:" < /tmp/diff.patch > /tmp/review.md
# then post /tmp/review.md as a PR comment (e.g. with the gh CLI or an action)

(Exact headless invocation may differ — see the docs. The principle is: feed the diff, capture Markdown, post it.)

The guardrails (read Hardening Autonomous Runs)​

Least privilege in CI
  • Comment only. Grant pull-requests: write, not contents: write — the bot shouldn't push code.
  • Scope the token; never expose deploy/secret access to a job that reads untrusted PR content.
  • Treat PR content as untrusted — it can carry prompt injection; don't let the job take consequential actions.
  • Cap cost — large diffs cost tokens; consider reviewing changed files only.

Make it useful, not noisy​

  • Ask for high-confidence findings only — a wall of nitpicks gets ignored.
  • Keep it as a first pass, with humans making the merge call.

The review prompt — tuned for a comment people actually read

Review this diff for correctness bugs, missing edge cases, and security issues.

Rules for your output:
- Report ONLY high-confidence findings. If you are unsure, leave it out.
- One Markdown checklist item per finding, each starting with file:line.
- State the concrete failure: what input or state leads to what wrong result.
- No style nitpicks, no praise, no summary of what the diff does.
- If you find nothing high-confidence, say exactly that in one line.

Diff:

Check yourself

0/3
  1. Your review job needs to post a comment on the pull request. Which permissions block is correct?
  2. Why does the checkout step use fetch-depth: 0?
  3. A contributor opens a PR whose description tells the reviewer to ignore the security checks and approve. What is the right posture?
Key takeaways
  • The whole job is: full-depth checkout, diff to a file, one headless pass, post the Markdown. Four steps, one of which needs write access.
  • pull-requests: write and contents: read. Never contents: write in a job that reads contributor-authored content.
  • fetch-depth: 0, or the diff is empty and the review reads like a clean bill of health.
  • PR content is untrusted and can carry prompt injection. Least privilege is the control; prompt wording is not a boundary.
  • Ask for high-confidence findings only, with file:line. A wall of nitpicks gets collapsed and the automation dies of being ignored.
  • Cap the cost as diffs grow — reviewing changed files only is the usual lever.
  • It is a first pass. Humans still make the merge call.

Next​