Skip to content

Set Up Claude as a PR Reviewer with the Official GitHub Action

A hardened, paste-ready setup for adding Anthropic's claude-code-action to a GitHub repo, with the security and cost knobs spelled out for production use.

Small teams with one bottleneck reviewer end up merging PRs late, with the reviewer skimming at the end of the day. The boring class of bugs (missing null check, swallowed error, wrong logger, a test added but never executed) is exactly what a careful first-pass reader catches in thirty seconds, and exactly what a tired human glides past. The Anthropic-maintained anthropics/claude-code-action is the right default for an AI first-pass reviewer today: lowest-friction setup, inherits Anthropic's auth and prompt-injection hardening, and composes with normal branch protection without itself becoming a required reviewer.

This post is for backend and full-stack engineers who already write GitHub Actions YAML and want a paste-ready setup, plus the security and cost levers to make it safe on a production repo.

Drop the workflow at .github/workflows/claude-review.yml. The file below is the minimum I'd ship to a real repository: triggered on pull_request (not pull_request_target), with the minimum permission set the action needs (contents: read, plus pull-requests: write and issues: write for posting comments), a tight claude_args block, and the model and turn limit pinned.

yaml
name: Claude Code Reviewon:  pull_request:    types: [opened, synchronize, reopened]
permissions:  contents: read  pull-requests: write  issues: write
jobs:  review:    runs-on: ubuntu-latest    steps:      - uses: actions/checkout@v4        with:          fetch-depth: 0      - uses: anthropics/claude-code-action@v1        with:          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}          prompt: |            Review this pull request for correctness, missing error handling,            and obvious security issues. Post findings as review comments.            Skip style nits.          claude_args: "--max-turns 5 --model claude-sonnet-4-6 --allowedTools Read,Grep,Glob"

The two non-obvious choices are worth naming. First, fetch-depth: 0 on the checkout step gives the action access to the full diff history; without it, large PRs get truncated context. Second, claude_args is the v1 way to pass CLI flags through to the underlying Claude Code runtime. In v0.x these were top-level inputs (model, max_turns, allowed_tools); pasting a v0.x example into a v1 workflow looks like it works because GitHub Actions does not error on unknown inputs, but the values silently no-op. Always check the action's migration guide if you copy from a tutorial older than the v1 GA.

To install the secret without leaving the editor, run claude /install-github-app from a local Claude Code session. This walks you through installing the official GitHub App at https://github.com/apps/claude and writes ANTHROPIC_API_KEY to repo secrets. You need repo admin to do this; if you don't, ask whoever does to set the secret manually and skip the GitHub App.

The interactive @claude loop

The first job handles automation: every PR open and push gets a review. The second job handles interactive questions. When a teammate replies to Claude with @claude please re-check the auth flow after my last commit, the action picks up the mention and replies in-thread.

The mode switch is automatic in v1. When the action is triggered by pull_request and a prompt is set, it runs in automation mode. When triggered by issue_comment or pull_request_review_comment without a prompt, it listens for @claude mentions and runs in interactive mode.

yaml
  mention:    if: |      (github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@claude')) ||      (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude'))    runs-on: ubuntu-latest    steps:      - uses: actions/checkout@v4        with:          fetch-depth: 0      - uses: anthropics/claude-code-action@v1        with:          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}          claude_args: "--max-turns 8 --model claude-sonnet-4-6 --allowedTools Read,Grep,Glob"

You'll also want to add issue_comment and pull_request_review_comment to the on: block at the top of the file for this job to fire. The github.event.issue.pull_request guard on the issue_comment branch is load-bearing: GitHub fires issue_comment for both issues and pull request threads, and that property is non-null only on PR threads, so without it the action would also reply to plain-issue mentions and burn tokens on unrelated discussions. The action posts as a regular GitHub comment, with optional progress checkboxes that update live when you opt in via track_progress: true. It can also leave inline review comments and suggestion blocks when the prompt asks for them.

Cost

A useful heuristic: input tokens are roughly the diff length in lines times four, plus whatever the model pulls in via Read and Grep for context. Output is the length of comments produced. A 200-LOC, 5-file PR is typically a few thousand input tokens and a few hundred output. A 1500-LOC, 30-file PR can blow past 50K input on a thorough review. Prompt caching helps on repos reviewed repeatedly, since the action can re-use the cached repo context.

Verify pricing on the Anthropic pricing page before you publish numbers internally; rates change. As of this writing:

ModelInput (per MTok)Output (per MTok)
Haiku 4.5USD 1USD 5
Sonnet 4.6USD 3USD 15
Opus 4.7USD 5USD 25

Sonnet is the right default for general review: enough depth to catch real issues without the Opus tax. Haiku makes sense on high-volume monorepos where fast feedback beats deeper analysis. Opus is worth it on security-sensitive code where missed bugs are expensive. The cost-control levers worth setting from day one:

  • --max-turns 5 caps the agent loop so a confusing PR can't iterate indefinitely.
  • A paths: filter on the workflow trigger skips review on docs-only PRs.
  • A [skip-claude] commit-message convention via an if: guard lets authors opt out for a trivial change.
  • --fallback-model in claude_args drops to a cheaper model when the primary is rate-limited; CI bursts on Monday morning are the obvious trigger.

Security hardening

This is the section that justifies the post. The defaults above are safe, but it's worth knowing why each one is set the way it is, because the wrong values are easy to copy-paste from older tutorials.

Use pull_request, not pull_request_target. The latter runs in the context of the base branch with secrets available, which means a PR from a fork can run untrusted code with access to ANTHROPIC_API_KEY. This is the canonical "pwn request" exfiltration class GitHub Security Lab has documented at length. The action's built-in access control (it only triggers for users with write access to the repo) protects you on pull_request events; don't override it with allowed_non_write_users, which the docs explicitly call risky.

Don't grant contents: write. The default review job needs contents: read plus pull-requests: write and issues: write for posting comments. Adding contents: write gives the model the power to push to your branch, which you do not want unless you have a deliberate workflow that asks Claude to commit fixes. Add id-token: write only when you're using OIDC to AWS Bedrock or Google Vertex.

Set an explicit allowed_bots list, never '*'. GitHub Apps and bots cannot trigger the action by default. If you want Claude to run after Dependabot or a release bot, list the bot accounts explicitly. The docs warn that on a public repo, allowed_bots: '*' lets any GitHub App invoke the action with a prompt that the App controls, which is the same exfiltration shape as pull_request_target.

Mitigate prompt injection from external content. The action sanitizes HTML comments, invisible characters, image alt text, and hidden HTML attributes from input. The docs are upfront that new bypass techniques may emerge, so two extra knobs are worth using on public repos: include_comments_by_actor to allowlist whose comments reach Claude, and exclude_comments_by_actor to drop noisy bot comments like dependabot[bot] and renovate[bot]. Exclusion takes priority when an actor matches both.

Constrain --allowedTools. The biggest mistake in a public-facing AI workflow is leaving the tool allowlist open and getting open-ended Bash. A read-only review allowlist is Read,Grep,Glob. If you need git, narrow it: Bash(git diff:*) rather than Bash. Never grant unrestricted Bash on PR-triggered runs; the model can curl your secrets out of the runner if it's tricked into doing so.

Don't let claude[bot] count as a required reviewer. In branch protection rules, configure required reviewers as humans only. The AI review is advisory; the merge gate is human. This is the difference between a useful first pass and an automated rubber stamp.

Scope the secret. Set ANTHROPIC_API_KEY at the repo or environment level rather than org-wide. One compromised repo shouldn't burn the whole org's quota.

Pin the action. @v1 is fine for casual repos. For production or regulated repos, pin to a commit SHA so a compromised tag can't silently swap the action's behavior under you. This is standard supply-chain hygiene and applies to every third-party action you use.

When to drop down to claude-code-base-action

The top-level action wraps a lower-level action with GitHub-specific PR and issue logic. Most teams should never need to leave the top-level wrapper. Reach for the base action when one of these is true:

  • You want a GitHub status check that gates merge (pass/fail), not a PR comment. The base action exposes a conclusion output (success or failure) you can pipe into a downstream if: or use with actions/github-script.
  • You want a multi-step pipeline: lint, then claude review, then integration tests, with the review's structured output feeding the next step.
  • You need a non-comment artifact: emit findings as a JSON file, upload as a workflow artifact, or feed to a SIEM.
  • You want the model to write fixes to a branch and open a PR back at you with custom commit-message conventions.
  • You're stitching multiple Claude calls (one for security, one for performance) into a single review pass with deduplication.

The base action also exposes inputs the top-level action hides behind claude_args: system_prompt, append_system_prompt, disallowed_tools, settings, and claude_env. Useful when you want to harden the system prompt against jailbreak attempts or pass scoped env vars to MCP servers.

Common pitfalls

A short list of things I've watched go wrong:

  • Using pull_request_target because an old tutorial used it, and exposing ANTHROPIC_API_KEY to forked code.
  • Granting contents: write "just in case" and giving the model push access to the default branch.
  • Leaving allowedTools unset and getting open-ended Bash in a public repo.
  • Letting the action's bot push count as a required reviewer in branch protection.
  • Re-running the action on every push to a long-running PR branch and burning tokens. Gate on synchronize if you must, but consider a paths: filter or a [skip-claude] opt-out.
  • Forgetting fetch-depth: 0 on actions/checkout and getting a truncated diff.
  • Pricing surprise on a monorepo with 5000-LOC PRs. Cap with --max-turns or skip the workflow on large diffs.

Closing

The default workflow above holds for most teams: PR-triggered review, interactive @claude loop, Sonnet model, read-only tool allowlist, pull_request trigger, hard human gate on merge. Drop down to claude-code-base-action only when the output shape stops fitting (status check, file artifact, multi-step pipeline). The single next step worth taking is to git apply the workflow above into a low-stakes repo, open a small PR against it, and watch how the review reads before rolling it out broadly.

References

Related Posts