Build an AI Code Reviewer with Claude: A Practical Guide
What you're actually building
An AI code reviewer built on Claude is a script or CI job that sends a diff (or a full file) to Claude with a review-focused prompt, then posts the response back as PR comments, a Slack message, or a report. It's not a linter — it doesn't catch syntax errors — but it catches things linters can't: unclear naming, missing edge cases, security smells, inconsistent error handling, and logic that technically works but will confuse the next person who touches it.
This guide covers the parts that matter in practice: how to structure the prompt so reviews are consistent, how to feed it diffs instead of whole repos, how to get structured output you can post automatically, and how to wire it into GitHub Actions.
The core building block: prompt + diff
The simplest version is a single API call: system prompt defines the reviewer's role and constraints, user message contains the diff.
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"system": "You are a senior code reviewer. Review the following diff. Flag bugs, security issues, and unclear logic. Ignore formatting/style unless it hides a bug. Be specific and reference line numbers from the diff. If the diff is fine, say so briefly.",
"messages": [
{
"role": "user",
"content": "```diff\n@@ -12,7 +12,9 @@\n- return user.email\n+ if user.email is None:\n+ return \"\"\n+ return user.email.strip().lower()\n```"
}
]
}'
That works, but for a real reviewer you need three things this basic call doesn't give you: consistent structure, context beyond the diff, and reliable output you can parse programmatically.
Give the model context, not just the diff
A diff alone often lacks enough information — the model doesn't know what the function is supposed to do, what conventions the repo follows, or whether a "bug" is actually intentional. Pull in:
- The full changed file (not just the diff hunk), so the model sees surrounding code
- The PR description or commit message, so it understands intent
- A short repo-specific style note (naming conventions, error-handling patterns, banned libraries) — a few lines is enough, don't paste your whole style guide
const systemPrompt = `You are reviewing a pull request for a Node.js/TypeScript backend.
House rules: no console.log in production code, all async functions must handle rejections,
prefer named exports. Flag bugs and security issues. Ignore pure style nits.`;
const userMessage = `PR description: ${prDescription}
Full file after changes:
\`\`\`typescript
${fullFileContent}
\`\`\`
Diff:
\`\`\`diff
${diffContent}
\`\`\`
Review only the changed lines, using the full file for context.`;
Force structured output
If you want to post inline PR comments automatically, free-text review is hard to parse reliably. Ask Claude to return JSON with a defined schema and give it an example of the shape you want.
const systemPrompt = `You are a code reviewer. Return ONLY valid JSON matching this schema:
{
"summary": "one sentence overall assessment",
"issues": [
{"line": 42, "severity": "high|medium|low", "comment": "description"}
],
"approve": true|false
}
No markdown, no explanation outside the JSON.`;
Claude is generally good at following this instruction, but you should still wrap the parse in a try/catch and fall back to treating the raw response as a plain comment if JSON parsing fails — models occasionally add a stray sentence before the JSON.
Wiring it into GitHub Actions
A minimal CI workflow: on pull request, fetch the diff, call the API, post a comment.
name: AI Code Review
on: pull_request
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- run: git diff origin/${{ github.base_ref }}...HEAD > diff.txt
- run: node review.js diff.txt
env:
SUBTOAPI_KEY: ${{ secrets.SUBTOAPI_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
review.js reads the diff, calls Claude, and posts a comment via the GitHub API using @actions/github or a plain fetch to the PR comments endpoint. Keep the diff under a reasonable size — for very large PRs, split by file and review each separately rather than sending one giant payload; this also gives you per-file comments instead of one wall of text.
Handling multi-file PRs and large diffs
Two practical constraints show up quickly:
- Diff size vs. context window. Large PRs can blow past what's useful to review in one pass. Split by file, review each independently, and merge the results before posting. This also parallelizes well — fire off concurrent requests per file instead of one sequential call per PR.
- Noise from generated or vendored files. Exclude lockfiles, generated code, and
dist/output before building the diff. Reviewingpackage-lock.jsonwastes tokens and produces useless comments.
Where SubToAPI fits
If you're already paying for Claude access and want to build this reviewer as an internal tool without juggling separate API billing, SubToAPI turns your existing Claude access into a standard HTTPS API with an application key (sub_live_...). That means your CI job, Slack bot, or internal dashboard talks to one endpoint with normal Bearer auth, and you get usage metadata per request — useful if you want to track how much a review bot is costing you per PR or per repo. Setup is a few lines: see the quickstart and the messages endpoint docs. If you want reviewers to stream output live into a PR comment as it's generated, streaming works the same way as a normal chat completion.
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4-5",
max_tokens: 1024,
system: systemPrompt,
messages: [{ role: "user", content: userMessage }]
})
});
If you're a small team building this as a shared tool, seat-based plans (from pricing) mean everyone on the team can build against the same key without individual API accounts, and Team/Scale plans add the multi-seat dashboard for tracking who's calling what.
What this reviewer is good and bad at
Good at: catching missing null checks, inconsistent error handling, obvious security issues (SQL string concatenation, missing input validation), and explaining why a change might be risky in plain language a junior dev can act on.
Bad at: catching issues that require running the code, understanding your full architecture, or knowing business rules that aren't documented anywhere. Treat it as a fast first-pass reviewer that catches the obvious stuff before a human looks — not a replacement for human review on anything that matters.
Questions
Does an AI code reviewer replace human review? No. It's a fast first pass that catches obvious bugs, unclear logic, and security smells before a human reviewer spends time on the PR. Anything architecturally significant still needs a human.
Should I send the whole repo or just the diff? Send the diff plus the full content of changed files for context. Sending the whole repo wastes tokens and dilutes the model's attention — it reviews better when focused on what actually changed.
How do I get consistent, parseable output from Claude? Ask for JSON with an explicit schema in the system prompt, include an example of the expected shape, and parse defensively with a fallback to plain-text handling if parsing fails.