DevOps / AIConcept

A code review bot that is terse.

A GitHub Action that reads the diff, opens the changed files, and posts a review focused on bugs, missing tests and security. No flattery, no nitpicks.

Most AI review bots leave forty comments per PR and your team starts ignoring them inside a week. This one is tuned the other way. It reads the diff and the surrounding code, but it only posts a comment if it has something genuinely useful to say. Empty reviews are fine. Silence is the goal half the time.

Get a walkthrough
01 / 03

What it does

When a PR is opened or pushed, a GitHub Action fires. It pulls the unified diff from the GitHub API, fetches the changed files at the new SHA, and sends both to Claude with a prompt that says, in effect: only flag real problems. If a comment would make the author roll their eyes, do not write it.

The action then posts inline review comments on specific lines and a single summary comment at the top of the PR. If nothing is worth flagging, it posts nothing. The summary always includes a one-line verdict: ship, ship with edits, or block.

A small allowlist controls which paths get reviewed. Migrations, lockfiles and generated code are skipped. The whole thing fits in one workflow file plus a 200-line TypeScript script.

02 / 03

The problem it solves

Code review is the bottleneck on every team I have worked with. Senior engineers pile up review queues. Juniors wait days for feedback on a one-line typo. The fix is not to replace human review, it is to give the human a head start.

This bot takes the first pass: catches the obvious bugs, the missing null checks, the test that was written before the code change. The senior reviewer arrives to a PR that has already been pre-cleaned. Their time goes to architecture and intent, not catching that you forgot to await a promise.

03 / 03

Architecture

A single workflow, a single script, one Claude call per PR. The whole repo is fewer than 400 lines. It runs on the GitHub-hosted free tier; no self-hosted runner needed.

  • Trigger: pull_request opened, synchronize, ready_for_review.
  • Diff: pulled from the GitHub Compare API at the head SHA.
  • Context: the full text of every changed file at HEAD, capped at 200KB total.
  • Prompt: a strict reviewer persona, instructed to skip nits and flag only real issues.
  • Output: a JSON array of {file, line, severity, comment} plus a verdict.
  • Posting: GitHub Reviews API, one review per run, edits the previous one in place on subsequent pushes.
Code

The interesting bits.

yaml·.github/workflows/review.yml
name: AI Code Review

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

jobs:
  review:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

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

      - run: npm ci
        working-directory: .github/review-bot

      - run: node index.mjs
        working-directory: .github/review-bot
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          REPO: ${{ github.repository }}
          HEAD_SHA: ${{ github.event.pull_request.head.sha }}
typescript·.github/review-bot/index.mjs
import Anthropic from '@anthropic-ai/sdk'
import { Octokit } from '@octokit/rest'

const claude = new Anthropic()
const gh = new Octokit({ auth: process.env.GITHUB_TOKEN })

const [owner, repo] = process.env.REPO.split('/')
const pull_number = Number(process.env.PR_NUMBER)

const { data: files } = await gh.pulls.listFiles({ owner, repo, pull_number, per_page: 100 })
const reviewable = files.filter((f) =>
  !/^(.*lock|dist\/|.*\.snap|.*\.generated\.)/.test(f.filename) &&
  f.status !== 'removed' &&
  f.changes < 800,
)

const context = reviewable.map((f) => `### ${f.filename}\n\n${f.patch ?? ''}`).join('\n\n')

const SYSTEM = `You review pull requests. Rules:
- Flag real problems only: bugs, missing error handling, security issues, broken tests.
- No style nits. No "consider renaming". No praise.
- If nothing is worth flagging, return an empty issues array.
- Verdict is one of: ship, ship_with_edits, block.`

const res = await claude.messages.create({
  model: 'claude-sonnet-4-5',
  max_tokens: 1500,
  system: SYSTEM,
  messages: [{ role: 'user', content: context }],
})

const { issues, verdict, summary } = JSON.parse(res.content[0].text)

if (issues.length === 0 && verdict === 'ship') process.exit(0)

await gh.pulls.createReview({
  owner, repo, pull_number,
  event: verdict === 'block' ? 'REQUEST_CHANGES' : 'COMMENT',
  body: `**Verdict: ${verdict}**\n\n${summary}`,
  comments: issues.map((i) => ({
    path: i.file,
    line: i.line,
    body: `**${i.severity}**: ${i.comment}`,
  })),
})
Tech stack

Tools, picked deliberately.

GitHub ActionsClaude SonnetAnthropic SDKOctokitTypeScriptNode 20
Run it yourself

From clone to working.

01

Drop in the workflow

Copy review.yml into .github/workflows. Copy the review-bot folder into .github.

02

Add the secret

Add ANTHROPIC_API_KEY in the repo settings. The GITHUB_TOKEN is provided by the runner.

03

Tune the allowlist

Edit the ignore regex to skip whatever your repo generates. Lockfiles, snapshots and dist directories by default.

04

Set the verdict policy

If you want block to actually block merges, add a branch protection rule that requires the AI Code Review check.

05

Open a noisy PR

Open a deliberately broken PR. The bot should flag the real bugs and stay silent on the formatting noise.

06

Iterate the prompt

After a week, read what it flagged and what it missed. Tune SYSTEM until silence feels honest.

Want early access?

This one is in the workshop. The pattern is documented above; the open source release is planned. Email me for a walkthrough or early access.

Email me