πŸ“ Tutorials
Β· 4 min read

Build an AI Code Review Bot for GitHub Pull Requests


Code reviews are essential but time-consuming. What if every PR got an instant first pass from AI β€” catching bugs, suggesting improvements, and flagging security issues before a human reviewer even looks at it?

In this tutorial, we’ll build a GitHub Action that automatically reviews pull requests using Claude. It reads the diff, analyzes the changes, and posts review comments directly on the PR. Your team gets faster feedback, and human reviewers can focus on architecture and design instead of catching typos and missing null checks.

What we’re building

When someone opens a PR, our bot:

  1. Reads the diff of changed files
  2. Sends each file’s changes to Claude for review
  3. Posts inline comments on specific lines
  4. Adds a summary comment with an overall assessment

Step 1: Create the GitHub Action workflow

Create .github/workflows/ai-review.yml:

name: AI Code Review

on:
  pull_request:
    types: [opened, synchronize]

permissions:
  contents: read
  pull-requests: write

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm install @anthropic-ai/sdk @octokit/rest

      - name: Run AI Review
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          REPO_OWNER: ${{ github.repository_owner }}
          REPO_NAME: ${{ github.event.repository.name }}
          BASE_SHA: ${{ github.event.pull_request.base.sha }}
          HEAD_SHA: ${{ github.event.pull_request.head.sha }}
        run: node .github/scripts/ai-review.mjs

Step 2: Add your Anthropic API key

Go to your repo β†’ Settings β†’ Secrets and variables β†’ Actions β†’ New repository secret:

  • Name: ANTHROPIC_API_KEY
  • Value: your Anthropic API key

GITHUB_TOKEN is automatically provided by GitHub Actions β€” no setup needed.

Step 3: Build the review script

Create .github/scripts/ai-review.mjs:

import Anthropic from '@anthropic-ai/sdk';
import { Octokit } from '@octokit/rest';
import { execSync } from 'child_process';

const anthropic = new Anthropic();
const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });

const { PR_NUMBER, REPO_OWNER, REPO_NAME, BASE_SHA, HEAD_SHA } = process.env;

// Get the diff for this PR
function getDiff() {
  return execSync(`git diff ${BASE_SHA}...${HEAD_SHA}`, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
}

// Parse diff into per-file chunks
function parseDiff(diff) {
  const files = [];
  const chunks = diff.split(/^diff --git /m).filter(Boolean);

  for (const chunk of chunks) {
    const fileMatch = chunk.match(/^a\/(.+?) b\//);
    if (!fileMatch) continue;

    const filename = fileMatch[1];
    // Skip non-code files
    if (/\.(lock|svg|png|jpg|ico|woff)$/.test(filename)) continue;
    if (filename.includes('node_modules/')) continue;

    files.push({ filename, patch: chunk.slice(0, 8000) }); // truncate large diffs
  }
  return files;
}

// Review a single file with AI
async function reviewFile(file) {
  const message = await anthropic.messages.create({
    model: 'claude-sonnet-4-20250514',
    max_tokens: 1024,
    messages: [{
      role: 'user',
      content: `Review this code diff. Focus on:
- Bugs and logic errors
- Security issues
- Performance problems
- Missing error handling

Be concise. Only comment on actual issues, not style preferences.
If the code looks good, respond with just "LGTM".

File: ${file.filename}
\`\`\`diff
${file.patch}
\`\`\``
    }],
  });
  return message.content[0].text;
}

// Post a comment on the PR
async function postComment(body) {
  await octokit.issues.createComment({
    owner: REPO_OWNER,
    repo: REPO_NAME,
    issue_number: parseInt(PR_NUMBER),
    body,
  });
}

// Main
async function main() {
  const diff = getDiff();
  if (!diff.trim()) {
    console.log('No diff found.');
    return;
  }

  const files = parseDiff(diff);
  if (files.length === 0) {
    console.log('No reviewable files.');
    return;
  }

  console.log(`Reviewing ${files.length} files...`);

  // Review files (max 10 to control costs)
  const reviews = [];
  for (const file of files.slice(0, 10)) {
    console.log(`  Reviewing ${file.filename}...`);
    const review = await reviewFile(file);
    if (review.trim() !== 'LGTM') {
      reviews.push({ filename: file.filename, review });
    }
  }

  // Build the comment
  let comment = '## πŸ€– AI Code Review\n\n';

  if (reviews.length === 0) {
    comment += 'βœ… No issues found. LGTM!\n';
  } else {
    for (const { filename, review } of reviews) {
      comment += `### \`${filename}\`\n\n${review}\n\n---\n\n`;
    }
  }

  comment += `\n<sub>Reviewed ${files.length} file(s) with Claude. This is an automated review β€” human review is still recommended.</sub>`;

  await postComment(comment);
  console.log('Review posted!');
}

main().catch(err => {
  console.error(err);
  process.exit(1);
});

Step 4: Test it

Push the workflow to your repo and open a pull request. Within a minute or two, you’ll see a comment from the bot with its review.

Tuning the prompt

The prompt is the most important part. Here’s how to adjust it for your team:

Stricter reviews β€” add specific rules:

Also check for:
- Console.log statements that should be removed
- TODO comments without issue links
- Functions longer than 50 lines

Framework-specific β€” add context:

This is a Next.js project using the App Router with TypeScript.
Check for proper use of 'use client' directives and server/client component boundaries.

Ignore certain patterns:

Ignore test files. Don't comment on import ordering.

Controlling costs

Each file review costs roughly $0.005-0.02. A typical PR with 5 files costs about $0.05-0.10. To keep costs down:

  • The script limits reviews to 10 files max
  • Lock files, images, and node_modules are skipped
  • Large diffs are truncated to 8,000 characters per file
  • Use claude-haiku instead of claude-sonnet for cheaper reviews (less accurate but 10x cheaper)

For a team of 10 developers doing 5 PRs/day each, expect roughly $25-50/month.

Making it smarter

Only review changed lines

Instead of reviewing the entire file diff, extract just the added lines:

const addedLines = file.patch
  .split('\n')
  .filter(line => line.startsWith('+') && !line.startsWith('+++'))
  .join('\n');

Skip draft PRs

on:
  pull_request:
    types: [opened, synchronize, ready_for_review]

jobs:
  review:
    if: github.event.pull_request.draft == false

Add a label to skip review

jobs:
  review:
    if: "!contains(github.event.pull_request.labels.*.name, 'skip-ai-review')"

What you learned

  • How to build a GitHub Action that reacts to pull requests
  • How to parse git diffs programmatically
  • How to use the Anthropic SDK in a CI/CD context
  • How to post comments via the GitHub API with Octokit

The bot won’t replace human reviewers, but it catches the obvious stuff β€” missing error handling, potential null pointers, security issues β€” so your team can focus on the harder questions during review.

Related: built-website-with-ai-in-one-afternoon

πŸ“˜