Automated Code Review with Claude Code: A Working Setup
Automated Claude Code review means every diff in your repo gets a structured pass for bugs, security holes, and contract drift before a human reviewer spends a single minute on it. The setup below is the one we actually run: a local slash command for pre-push review, a read-only code-reviewer subagent, house review rules in CLAUDE.md, and a GitHub Actions workflow built on the official claude-code-action that comments on every pull request. Everything is copy-pasteable, and none of it needs more than the claude CLI, a git repo, and an API key.
One principle drives the whole design: deterministic checks stay deterministic. Formatting, import order, and type errors belong to Prettier, ESLint, and your compiler. Claude reviews the things a linter cannot: whether the error path actually works, whether the new endpoint checks authorization, whether the migration and the model still agree. If you point an LLM at style nits, you get noise, reviewer fatigue, and a team that stops reading the comments. Scope it to judgment calls and it earns its place in the pipeline.
What a Working Claude Code Review Setup Looks Like
Four layers, each independently useful, each cheap to add:
- A local slash command,
/review-diff, that reviews your branch against main before you push. Catches problems while the context is still in your head. - A dedicated code-reviewer subagent that Claude Code delegates to automatically after non-trivial edits, running in its own context window with read-only tools.
- Review standards written once in CLAUDE.md, so the local command, the subagent, and the CI workflow all enforce the same rules.
- A GitHub Actions workflow using anthropics/claude-code-action that reviews every non-draft pull request and posts findings as a single updating comment.
If you use GitLab, Jenkins, or Buildkite instead of GitHub Actions, layer four swaps for headless mode: claude -p in a CI job, covered later in this article.
Prerequisites
You need surprisingly little:
- Claude Code installed:
npm install -g @anthropic-ai/claude-code(or the native installer if you prefer no Node dependency). Verify withclaude --version. - Authentication: either run
claudeonce and log in, or exportANTHROPIC_API_KEYfor headless and CI use. Teams on AWS or GCP can route through Bedrock or Vertex AI instead. - The gh CLI installed and authenticated if you want local PR reviews and CI comments via
gh pr comment. - A repo with a stable base branch. Examples below assume
main; swap in yours.
That is the entire dependency list. No webhook servers, no bots to host, no third-party review SaaS.
A Local Claude Code Review Command You Run Before Every Push
Current Claude Code builds ship a built-in review skill: type /code-review inside a repo and it reviews the working diff, with effort levels that trade depth for speed, plus a separate /security-review command focused on vulnerabilities. The built-ins are a solid baseline. The reason to write your own command anyway is control: your severity taxonomy, your definition of blocking, your output format.
Custom slash commands are markdown files in .claude/commands/. Create .claude/commands/review-diff.md:
---
description: Review the current branch diff against main before pushing
allowed-tools: Bash(git diff:*), Bash(git log:*), Bash(git merge-base:*), Read, Grep, Glob
---
## Context
- Commits on this branch: !`git log --oneline main..HEAD`
- Changed files: !`git diff --name-only main...HEAD`
- Full diff: !`git diff main...HEAD`
## Task
Review this diff as a senior engineer who will be paged if it breaks.
Rules:
- Only report issues you are confident about. If you are unsure, say so in one line or stay silent.
- Severity levels: BLOCKER (correctness, security, data loss), MAJOR (will cause a bug or incident within months), MINOR (worth fixing, never worth blocking a merge).
- Never comment on formatting or style. Linters own that.
- Every finding must cite file and line from the diff and include a concrete suggested fix.
- Check that changed public functions have matching test changes. Flag any that do not.
- Read surrounding code with Read or Grep before flagging cross-file issues. Do not guess.
Output format: a findings list grouped by severity, then one verdict line: SHIP, SHIP WITH FIXES, or DO NOT SHIP.The lines starting with ! execute before the prompt runs and inject their output as context, which is why the allowed-tools frontmatter grants exactly those git commands and nothing else. The model starts with the full diff already in front of it instead of burning turns rediscovering it.
Usage is just /review-diff on your feature branch. Ten to sixty seconds later you have a severity-ranked findings list, while the code is still fresh in your head and fixing a BLOCKER costs five minutes instead of a review round-trip.
If you want this to run without remembering it, wire it into a git pre-push hook in advisory mode:
#!/bin/sh
# .git/hooks/pre-push (chmod +x)
claude -p "Review the diff between origin/main and HEAD. Report BLOCKER issues only, with file:line. If there are none, print exactly: OK" \
--model sonnet \
--max-turns 10 \
--allowedTools "Bash(git diff:*),Read,Grep"
exit 0Note the exit 0: it prints findings but never blocks the push. Blocking pushes on LLM output is a fast way to make your team hate the tool. Print, inform, let the human decide.
A Dedicated Code-Reviewer Subagent
Slash commands are something you invoke. Subagents are something Claude Code invokes on its own. Drop a file at .claude/agents/code-reviewer.md and every session in the repo gains a reviewer that gets delegated to automatically when its description matches the situation:
---
name: code-reviewer
description: Reviews code changes for correctness, security, and maintainability. Use proactively after writing or modifying any non-trivial code, before committing.
tools: Read, Grep, Glob, Bash
---
You are a senior code reviewer for this repository. You review diffs; you never edit files.
Process:
1. Run git diff to see the change (unstaged, staged, or against main, whichever is non-empty).
2. Read every changed file in full, not just the hunks. Grep for other call sites of changed functions.
3. Check: error handling on every new code path, authorization on every new endpoint or query, input validation at trust boundaries, race conditions around shared state, secrets or credentials in the diff, breaking changes to public interfaces without test updates.
Report findings by severity (BLOCKER, MAJOR, MINOR) with file:line and a suggested fix for each. Only report issues you are confident about. End with a one-line verdict.Three details matter here. The phrase "use proactively" in the description is what makes the main agent delegate without being asked, right after it finishes a coding task. The tools line deliberately omits Edit and Write, so the reviewer physically cannot "helpfully" rewrite your code mid-review; Bash is there for git commands only, and the system prompt says so. And because subagents run in their own context window, a 4,000-line diff gets reviewed without flooding the conversation you are working in; only the findings come back.
You can also pin the subagent to a different model with a model field in the frontmatter, which sets up a nice pattern: do the coding on your default model, run review passes on a heavier one for risky areas.
Teach the Reviewer Your Standards in CLAUDE.md
Every entry point reads the repo CLAUDE.md: interactive sessions, slash commands, subagents, and the GitHub Action in CI. That makes it the one place to define what "reviewed" means in your codebase, instead of copy-pasting rules into three prompts that drift apart. Add a section like this:
## Code review standards
- Severity: BLOCKER = correctness, security, or data loss. MAJOR = will
bite within a quarter. MINOR = cleanup, never blocks a merge.
- Never flag formatting or naming style. Prettier and ESLint own those.
- Every finding cites file:line and proposes a concrete fix.
- Changed public APIs require changed tests. No exceptions.
- All SQL goes through the query builder; raw string SQL is a BLOCKER.
- Anything touching src/payments/ or src/auth/ gets maximum scrutiny;
assume an attacker reads this code.
- Cap output at the 10 most important findings. Signal beats volume.Two pieces of advice from running this for a while. First, make every rule checkable, not aspirational: "raw string SQL is a BLOCKER" gets enforced; "write clean code" gets ignored. Second, the cap on findings is load-bearing. Without it, large diffs produce twenty-item lists where the two real problems drown in MINOR nitpicks, and reviewers learn to skim. Ten findings maximum forces triage inside the model instead of on your screen.
Automated Claude Code Review on Every Pull Request
Now the layer that covers the whole team. The fastest path is running /install-github-app inside Claude Code: it walks through installing the Claude GitHub app on your repo, saving ANTHROPIC_API_KEY as a repo secret, and scaffolding workflow files, including an optional automatic review workflow. If you prefer to own the YAML, here is a complete .github/workflows/claude-review.yml:
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize, ready_for_review]
paths-ignore:
- "**/*.lock"
- "package-lock.json"
- "dist/**"
- "**/*.generated.*"
concurrency:
group: claude-review-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
claude-review:
if: >
github.event.pull_request.draft == false &&
github.actor != 'dependabot[bot]'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: read
id-token: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
use_sticky_comment: true
prompt: |
Review pull request #${{ github.event.pull_request.number }} in
${{ github.repository }}.
Read the diff with: gh pr diff ${{ github.event.pull_request.number }}
Read the PR description with: gh pr view ${{ github.event.pull_request.number }}
Apply the code review standards from CLAUDE.md. Report findings
by severity (BLOCKER, MAJOR, MINOR) with file:line and a
suggested fix. Only report issues you are confident about, cap
the list at 10, and never comment on style. End with one verdict
line: SHIP, SHIP WITH FIXES, or DO NOT SHIP.
Post the review as a PR comment using gh pr comment.
claude_args: |
--model sonnet
--max-turns 25
--allowedTools "Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr comment:*),Read,Grep,Glob"What each piece buys you:
use_sticky_comment: truemakes the action update one review comment per PR instead of stacking a new comment on every push. This single flag is the difference between a tool people read and a tool people mute.- The
concurrencyblock cancels an in-flight review when a new commit lands, so you never pay for reviewing a stale diff. - The
ifguard skips drafts and dependabot PRs. Reviewing a lockfile bump with an LLM is pure waste. paths-ignorekeeps generated files and lockfiles from triggering runs at all.claude_argspasses flags straight to the CLI inside the runner: model choice, a turn budget so a pathological run cannot spin forever, and an allowlist that grants exactly the gh commands the prompt needs.
The action also supports inline review comments on specific lines: it exposes an MCP tool named mcp__github_inline_comment__create_inline_comment which you can add to the --allowedTools list and reference from the prompt ("attach each finding as an inline comment on the relevant line"). Teams tend to have strong opinions here; we default to the single sticky comment and turn on inline comments only for repos that ask for them.
The same action doubles as an on-demand reviewer: the default @claude workflow that /install-github-app creates responds to mentions, so anyone can comment "@claude review the error handling in this PR" and get a targeted pass without waiting for the automatic run.
Headless Claude Code Review in GitLab or Any Other CI
Everything the GitHub Action does reduces to one CLI feature: print mode. claude -p runs a full agentic session non-interactively and exits, which makes it embeddable in any CI system:
git fetch origin main
claude -p "Review the diff between origin/main and HEAD for correctness, security, and maintainability issues, following the code review standards in CLAUDE.md. Output markdown findings with severity and file:line. If there are no findings, output exactly: NO FINDINGS" \
--model sonnet \
--max-turns 20 \
--allowedTools "Bash(git diff:*),Bash(git log:*),Read,Grep,Glob" \
--output-format json > review.json
jq -r '.result' review.jsonWith --output-format json you get a structured envelope instead of raw text: the review lives in .result, and the payload includes fields like is_error, num_turns, and total_cost_usd, which is exactly what you want for logging what each review actually cost. A minimal GitLab CI job wrapping this:
claude-review:
stage: test
image: node:22
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
script:
- npm install -g @anthropic-ai/claude-code
- git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
- claude -p "Review the diff between origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME and HEAD per CLAUDE.md review standards. Severity-ranked findings with file:line. If none, print exactly NO FINDINGS" --model sonnet --max-turns 20 --allowedTools "Bash(git diff:*),Read,Grep,Glob" --output-format json > review.json
- jq -r '.result' review.json
artifacts:
paths:
- review.json
allow_failure: trueSet ANTHROPIC_API_KEY as a masked CI/CD variable. Note allow_failure: true: same philosophy as the pre-push hook, the review informs the merge request but does not gate it. If you do want a hard gate, grep the result for BLOCKER and exit non-zero on a match, but do that only after a few weeks of watching the false-positive rate:
if jq -r '.result' review.json | grep -q "BLOCKER"; then
echo "Blocking findings detected"
exit 1
fiThe same headless pattern works in Jenkins, Buildkite, CircleCI, or a bare cron job; the only moving parts are the API key, the fetched base branch, and the JSON file.
Keeping Automated Reviews Fast, Cheap, and Quiet
Cost and noise are the two ways a claude code review pipeline dies. Both are controllable:
- Review diffs, never repos. Every prompt above anchors on
git difforgh pr diff. Cost scales with change size, not codebase size, and a shallowfetch-depth: 1checkout keeps runners fast. - Match the model to the lane. Model aliases (
--model haiku,--model sonnet,--model opus) let you run routine PR triage on a fast model and reserve the heavyweight for paths like auth and payments, without hardcoding version strings that rot. - Budget turns.
--max-turnscaps how long a review can wander. Around 20 turns is plenty to read a diff, grep call sites, and write findings. - Filter triggers hard. Draft skip, bot skip,
paths-ignore, and concurrency cancellation each remove a class of pointless runs before a single token is spent. - Cap findings and severity-gate comments. Ten findings max in the prompt, and if you post inline comments, post them only for BLOCKER and MAJOR.
- Measure instead of guessing. The
total_cost_usdfield in the JSON envelope, aggregated over a week ofreview.jsonartifacts, tells you exactly what the pipeline costs, so decisions about model choice are grounded in your own numbers.
What Claude Code Review Catches, and What It Misses
After months of running this setup, a fair scorecard. Claude code review is genuinely strong at: unhandled error paths and swallowed exceptions, missing authorization checks on new endpoints, SQL and command injection patterns, secrets committed in diffs, off-by-one and boundary mistakes, breaking API changes with no matching test edits, concurrency hazards around shared state, and drift between code, comments, and docs in the same PR. It reads the whole diff every time with zero fatigue, which is precisely where tired human reviewers slip.
It is weak where context lives outside the repo: whether the feature matches what the customer asked for, performance regressions that only profiling reveals, architectural taste, and domain invariants nobody wrote down. It will also occasionally flag correct code as suspicious; the confidence rule and the findings cap keep that tolerable, but the false-positive rate never reaches zero.
Which is why the setup deliberately keeps deterministic tools in the loop. Claude Code hooks can run your linter automatically after every file edit, so the model never wastes review budget on what a machine already enforces. In .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx eslint --fix"
}
]
}
]
}
}Linters catch the mechanical, tests catch the specified, Claude catches the judgment layer, and humans stay on the merge button. Each layer covers a failure mode the others miss.
FAQ
Does automated Claude Code review replace human code review?
No, and running it that way backfires. Treat it as a triage layer: it clears the mechanical and semi-mechanical findings within a minute of the PR opening, so the human reviewer spends their attention on design, intent, and product correctness. Our merge rule is unchanged: a human approves every PR. What changed is what that human no longer has to hunt for.
What does it cost to run on every pull request?
It depends on diff size, model choice, and turn budget, so distrust anyone quoting a universal number. The honest answer is to instrument your own pipeline: run it with --output-format json, aggregate total_cost_usd for a week, and decide with real data. The levers that matter most are skipping drafts and bot PRs, paths-ignore, turn caps, and using a smaller model alias for routine changes.
Is it safe to point at private code?
Review your own org's policy first, but the mechanics are straightforward: the CLI and the GitHub Action send code to the API you configure, governed by Anthropic's commercial terms, and teams with stricter requirements can route the exact same setup through AWS Bedrock or Google Vertex AI (the action supports both). Also scope what the workflow can touch: the allowlists above grant a handful of read-only git and gh commands, nothing else.
How is this different from just typing /code-review locally?
Same engine, different guarantees. The built-in skill is a great interactive baseline, and /security-review is worth running before any release. The custom command, subagent, and CI workflow add the parts a built-in cannot know: your severity taxonomy, your blocking rules, your paths that deserve paranoia, and the guarantee that review happens on every PR whether or not anyone remembers to ask.
How do I stop it from reviewing lockfiles and generated code?
Three fences, use all of them: paths-ignore in the workflow trigger so those PRs never start a run, an explicit prompt rule to ignore generated files if they appear inside a mixed diff, and the findings cap so even when junk slips through it cannot crowd out real issues.
Does this work in a monorepo?
Yes, and it gets better there. Claude Code reads nested CLAUDE.md files, so each package can carry its own review standards next to its code. Split the GitHub workflow per package with paths filters if you want different models or stricter rules for, say, the billing service than for internal tooling.
Where to Start
Do not build all four layers in one afternoon. Add .claude/commands/review-diff.md today and use it for a week; it costs nothing to adopt and you will feel the value on the first BLOCKER it catches pre-push. Then write the CLAUDE.md standards section, since everything else inherits it. Add the subagent when you notice you want reviews you did not ask for, and turn on the GitHub workflow once the local prompt has earned your trust. At that point you have a claude code review pipeline that reviews every change, costs what you decided it should cost, and leaves humans doing the only part of code review that ever needed a human.
AI CodingShip full-stack AI apps at conversation speed — specs, agents, deploys, all from the terminal.
Claude CodeGo from zero to confident with Claude Code, the terminal agent that reads, edits, runs, and verifies real code.