How to Build a Code Review Bot with Claude
What a Claude code review bot actually does
A code review bot built on Claude listens for pull request events (opened, updated), fetches the diff, sends it to Claude with a review-focused prompt, and posts the response back as PR comments. It doesn't replace human reviewers — it catches obvious bugs, style inconsistencies, missing error handling, and security issues before a human spends time on the PR.
The core pieces you need are: a webhook receiver for your git provider (GitHub, GitLab, Bitbucket), a diff fetcher, a prompt that produces structured, actionable feedback, and a way to call Claude's API reliably with retries and streaming if you want live feedback in a CI log. Below is a working setup you can adapt in an afternoon.
Step 1: Receive the pull request event
Set up a webhook on your repo for pull_request events. A minimal Express handler looks like this:
app.post("/webhook/github", async (req, res) => {
const event = req.body;
if (event.action === "opened" || event.action === "synchronize") {
const diff = await fetchDiff(event.pull_request.diff_url);
await reviewAndComment(event, diff);
}
res.sendStatus(200);
});
fetchDiff just does a GET request against the diff URL with your GitHub token in the headers. Keep diffs under a reasonable size — if a PR touches 50 files, consider chunking by file rather than sending the whole diff in one request.
Step 2: Write a review prompt that produces usable output
The quality of your bot depends almost entirely on the prompt. Vague instructions like "review this code" produce vague feedback. Be specific about what to look for and the output format:
const systemPrompt = `You are a senior code reviewer. Review the diff below.
Only comment on:
- bugs or logic errors
- missing error handling
- security issues (injection, secrets, unsafe deserialization)
- obvious performance problems
Ignore style/formatting unless it hides a bug.
Output a JSON array of objects: { "file": string, "line": number, "comment": string, "severity": "low"|"medium"|"high" }.
If there is nothing worth flagging, return an empty array.`;
Forcing structured JSON output makes it trivial to post inline comments instead of one giant wall of text, and it lets you filter by severity so only "high" issues block a merge if you want that gate.
Step 3: Call the API
Here's a call using SubToAPI, which gives you a stable sub_live_... key, streaming, and usage metadata on top of your existing Claude access — useful if the bot runs across multiple repos or teams and you want one dashboard for cost and rate limits instead of juggling raw provider keys:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 2048,
"system": "You are a senior code reviewer...",
"messages": [
{ "role": "user", "content": "Diff:\n```\n'"$DIFF"'\n```" }
]
}'
The request/response shape follows the standard Messages format, so if you've used the Claude API directly, migration is a find-and-replace on the base URL and key. See /docs/messages for the full request schema and /docs/quickstart if you're starting from zero.
Step 4: Parse the response and post comments
Once you get the JSON array back, map it onto the PR's review comment API:
const comments = JSON.parse(claudeResponse.content[0].text);
for (const c of comments) {
await octokit.pulls.createReviewComment({
owner, repo,
pull_number: pr.number,
commit_id: pr.head.sha,
path: c.file,
line: c.line,
body: `**[${c.severity}]** ${c.comment}`,
});
}
Wrap the JSON parse in a try/catch — models occasionally wrap JSON in markdown fences or add a trailing sentence. A simple regex strip of ``` `json ``` fences before parsing handles most of these cases.
Step 5: Handle large diffs and streaming for CI logs
If you're running the bot inside a CI pipeline rather than as a webhook, you often want to stream output to the job log so engineers see progress instead of a silent multi-second hang. Claude's API supports server-sent events for this, and SubToAPI passes streaming through unchanged — see /docs/streaming for the event format if you want live token output in your CI console.
For large diffs, split by file and run reviews in parallel rather than concatenating everything into one giant prompt. This keeps individual requests under token limits and makes failures isolated — one file failing to parse doesn't kill the whole review.
Step 6: Add guardrails before trusting the bot in production
A few practices that matter once this is running on real repos:
- Don't auto-merge on a clean review. Treat the bot as a first pass, not a gate, unless you've validated it against months of real PRs.
- Rate limit per repo. A bot that fires on every push to every branch burns through quota fast. Only trigger on PR open/update events, not every commit.
- Log every review. Store the diff, prompt, and response somewhere so you can audit false positives and tune the prompt over time.
- Version your prompt. Treat the system prompt like code — commit it, review changes to it, and roll back if a new version produces worse feedback.
Step 7: Give the bot tool access for deeper checks (optional)
If you want the bot to actually run tests or look up function definitions instead of reasoning from the diff alone, use tool calling. Define a tool like read_file or run_tests that your bot executes when Claude requests it, then feed the result back in a follow-up message. This turns a surface-level diff review into something closer to an agent that can trace a function across files before flagging it. Details on defining and handling tool calls are in /docs/tools.
Pulling it together
A production-ready version of this bot is a few hundred lines: a webhook receiver, a diff chunker, a prompt you iterate on, a parser with fallback handling, and an API client with retries. Start with the simplest version — one prompt, one API call per PR, plain text output — and only add structured output, tool calling, and parallel file reviews once you've confirmed the basic loop produces feedback your team actually finds useful. Check /pricing if you're estimating cost across multiple repos, and /signup gets you a key and free trial to test the full flow end to end.
FAQ
Does Claude need my repo's full codebase to review a PR? No — for most reviews, the diff plus a short file-path context is enough. Full codebase access only helps for cross-file reasoning, which is better handled with tool calling to fetch specific files on demand rather than stuffing everything into one prompt.
How do I stop the bot from commenting on every tiny style nitpick? Constrain the prompt explicitly to bugs, security, and error handling, and tell it to ignore formatting unless it hides a real issue. Also filter the output by severity before posting — only show "medium" and "high" by default.
Can this run on GitLab or Bitbucket instead of GitHub? Yes — the webhook payload and comment API differ per provider, but the Claude integration (prompt, API call, response parsing) is identical. Swap the diff-fetching and comment-posting code for the provider's SDK and keep the rest of the pipeline unchanged.