← Blog

How to Build a Claude-Powered Code Review Bot

2026-11-07 · 5 min read · SubToAPI Team

Building a Claude-powered code review bot means wiring three pieces together: a trigger (usually a pull request webhook), a prompt that feeds Claude the diff plus repo context, and a way to post Claude's findings back as PR comments. None of this is exotic — it's a webhook handler, an HTTPS call to a Claude-compatible endpoint, and a GitHub API call to write comments.

The rest of this guide walks through each piece concretely: how to structure the diff for review, how to prompt Claude so it gives actionable feedback instead of generic praise, how to use tool calling so the bot can fetch related files, and how to post results back to GitHub or GitLab. We'll use SubToAPI's HTTPS endpoint for the Claude calls so the bot runs as a small, stateless service with no infrastructure to manage.

Architecture overview

A minimal review bot needs:

  1. A webhook receiver that fires on pull_request (opened/synchronize) events.
  2. A diff fetcher that pulls the changed files and patch content.
  3. A call to Claude with the diff and review instructions.
  4. A comment poster that writes the response back to the PR.
GitHub/GitLab webhook → your server → fetch diff → call Claude → post comments

You can run this as a serverless function (AWS Lambda, Cloudflare Workers, Vercel) or a small Node/Express service. Statelessness matters here — each PR event is independent, so you don't need a database unless you want to track review history.

Step 1: Fetch the diff

GitHub's API gives you the diff directly. For a pull request:

const res = await fetch(
  `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/files`,
  { headers: { Authorization: `Bearer ${GITHUB_TOKEN}` } }
);
const files = await res.json();

const diffText = files
  .map(f => `File: ${f.filename}\n${f.patch || "(binary or too large)"}`)
  .join("\n\n");

Cap the diff size — large PRs can blow past context limits and also produce review comments that are too generic to be useful. A sensible rule: review files individually if the whole diff exceeds a few thousand lines, or skip generated/vendored files entirely (package-lock.json, dist/, etc.) before sending anything to Claude.

Step 2: Prompt Claude for structured feedback

The quality of the bot depends almost entirely on the prompt. Ask for specific, actionable output, and constrain the format so you can parse it reliably.

const systemPrompt = `You are a senior engineer doing a code review.
Review the diff below. For each issue, output:
- file and line (if identifiable)
- severity: blocker, warning, or nit
- a one-sentence explanation
- a suggested fix if applicable

Skip style nits already enforced by a linter. Focus on correctness,
security, and maintainability. If the diff looks fine, say so briefly.`;

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: 2000,
    system: systemPrompt,
    messages: [
      { role: "user", content: `Diff:\n\n${diffText}` }
    ]
  })
});

const data = await response.json();
const reviewText = data.content[0].text;

This is a standard Messages-style call — see /docs/messages for the full request shape. Because SubToAPI exposes a plain HTTPS endpoint with an application API key (sub_live_...), the bot doesn't need Claude credentials embedded anywhere beyond the one key, which makes rotating or scoping access per project straightforward.

Step 3: Give Claude more context with tool use

A diff alone is often not enough — Claude can't see the full file, related tests, or how a function is used elsewhere. Tool calling fixes this: define a get_file_contents tool that the bot executes on Claude's request, so Claude can pull in whatever context it decides it needs before finalizing the review.

const tools = [
  {
    name: "get_file_contents",
    description: "Fetch the full contents of a file from the repository",
    input_schema: {
      type: "object",
      properties: { path: { type: "string" } },
      required: ["path"]
    }
  }
];

When Claude responds with a tool_use block, your server resolves it by calling the GitHub Contents API, then sends the result back in a follow-up message with a tool_result block. This loop lets the bot check how a changed function is called elsewhere in the codebase before flagging it as a breaking change — far more reliable than reviewing a diff in isolation. Full request/response shapes for this pattern are in /docs/tools.

Step 4: Post comments back to the PR

Parse Claude's structured output and post it as a single summary comment, or split it into inline comments per file/line using GitHub's review API:

await fetch(
  `https://api.github.com/repos/${owner}/${repo}/issues/${prNumber}/comments`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${GITHUB_TOKEN}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ body: reviewText })
  }
);

For inline comments tied to specific lines, use the /pulls/{pr}/reviews endpoint with comments: [{ path, line, body }] — this requires Claude's output to include file and line numbers, which is why the prompt above explicitly asks for them.

Streaming for faster feedback

If your bot runs in a UI context (a Slack command, a dashboard) rather than purely as a webhook responder, you can stream the review as it's generated instead of waiting for the full response. SubToAPI supports server-sent events on the same endpoint — see /docs/streaming for the event format.

Keeping it in one dashboard instead of raw Claude credentials

If you're running this bot across multiple repos or for a team, the practical pain points aren't the review logic — they're credential management, rate limits, and knowing what each request cost. SubToAPI wraps your existing Claude access in an HTTPS API with scoped application keys, usage metadata per request, and team seats, so you can give each repo's bot its own key and see usage without building that tooling yourself. Plans start at €9/month for Solo, with Team (€19/seat) and Scale (€49/seat) tiers for larger setups — see /pricing. There's a free trial at /signup, and /docs/quickstart covers the full setup in a few minutes.

Questions

Do I need the full GitHub App permissions model, or can a personal access token work? A fine-grained PAT with pull_request:write and contents:read is enough for a single-repo bot. For multi-repo or org-wide deployment, a GitHub App is cleaner because it scopes permissions per installation and avoids tying the bot to one person's account.

How do I stop the bot from commenting on every tiny PR? Filter in your webhook handler before calling Claude: skip PRs under a certain diff size, skip draft PRs, or require a label like needs-review. This also reduces API costs since you're not reviewing noise.

Can the bot re-review after the author pushes fixes? Yes — trigger on synchronize events too, and diff only the new commits since the last review (GitHub gives you the base/head SHAs) so Claude isn't re-reading the whole PR every push.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →