Automated code review on Claude via API

Setting up Claude in GitHub Actions to comment on pull requests, without double-charging on job retries or blowing the budget on one huge diff.

4 min · · Updated · development · код-ревью

Reviewing pull requests with a bot boils down to one operation: take the diff, send it to a model with "find bugs and issues," get back a list of comments. The hard part isn't the prompt — it's making sure this doesn't break in CI or drain the budget on the first large PR.

Which endpoint and which model

Code review calls for /v1/messages (the Anthropic Messages API) — it gives more control over the system prompt than chat completions. Use claude-sonnet-5 for regular PRs and claude-opus-5-5 for critical branches like main or release/*, where review quality matters more than speed. Both carry a 1,000,000-token context window, so a diff of a couple thousand lines fits whole, no chunking required.

bash
curl https://api.zukko.pro/v1/messages \
  -H "Authorization: Bearer fetch_your_key" \
  -H "Content-Type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-sonnet-5",
    "max_tokens": 2000,
    "system": "You are a code reviewer. Look for bugs, races, resource leaks, security issues. Skip formatting comments. Reply as a list: file, line, issue, suggested fix.",
    "messages": [{"role": "user", "content": "'"$(git diff origin/main... | jq -Rs .)"'"}]
  }'

Idempotency is not optional in CI

GitHub Actions retries failed jobs. Without protection, a job retry on the same commit sends the same diff a second time and charges twice. The fix is an Idempotency-Key header built from the PR and commit hash:

bash
IDEMPOTENCY_KEY="review-$(git rev-parse HEAD)-$PR_NUMBER"

curl https://api.zukko.pro/v1/messages \
  -H "Authorization: Bearer fetch_your_key" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d @payload.json

Within 24 hours, a repeated request with the same key returns the same response without a second charge. This is exactly the case where a runner retry or a network timeout on the CI side shouldn't turn into a double bill.

Estimating cost before you run it

A 50-file diff can run into hundreds of thousands of tokens, and on a large monorepo that adds up per PR. Before sending it off for review, it's worth running the text through the free token count:

bash
curl https://api.zukko.pro/v1/messages/count_tokens \
  -H "Authorization: Bearer fetch_your_key" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{"model": "claude-sonnet-5", "messages": [{"role": "user", "content": "..."}]}'

If the number clears a sane threshold — say, 300,000 tokens — it makes more sense to trim the diff down to changed functions plus their calling context, or to switch to per-file review instead of reviewing the whole PR at once.

Budget control through the key

The CI bot should run on its own API key, not a shared one. In the dashboard (https://zukko.pro/app/api-keys) a key can carry a daily or monthly dollar spend cap and a list of allowed models. The practical benefit: if a recursive call slips into the pipeline, or someone sets review-on-every-push instead of review-on-PR, the key hits key_daily_limit_reached (429) instead of draining the whole account balance overnight.

bash
{"error": {"message": "Daily limit reached for this key", "code": "key_daily_limit_reached"}}

Handle this in the script by not failing loudly but skipping the review gracefully, leaving a comment like "daily limit reached, review skipped" instead of a red X scaring developers.

What to log from response headers

Two response headers are useful for monitoring the bot: X-Zukko-Cost-Micros, the cost of that specific call in micro-dollars, and X-Fetch-Balance-Cents, the remaining balance (despite the name, in microdollars: 1,000,000 = $1; the exact balance in cents comes from GET /v1/balance, the balance_cents field). Both headers only come with regular, non-streaming responses. Logging the first per PR makes it easy to chart "review cost per month" and compare it against reviewer hours.

bash
import requests

resp = requests.post(
    "https://api.zukko.pro/v1/messages",
    headers={
        "Authorization": "Bearer fetch_your_key",
        "anthropic-version": "2023-06-01",
    },
    json=payload,
)
cost_usd = int(resp.headers.get("X-Zukko-Cost-Micros", 0)) / 1_000_000
print(f"Review cost: ${cost_usd:.4f}")

Caching for repeated runs

If the pipeline re-runs review every time a new comment is posted on a PR, and the diff hasn't changed since last time, it's worth enabling response caching with the X-Zukko-Cache: true header — without temperature: 0: claude-sonnet-5 and claude-opus-5-5 may reject a temperature other than 1 with a 400. A request with a fully identical body from the same user caches for up to 7 days, and a repeat call returns instantly and for free, with X-Zukko-Cache: hit in the response (such hits aren't written to /v1/usage); "almost identical" requests don't hit the cache. That doesn't fit review itself, since the prompt always carries the current diff, but it works well for re-analyzing an unchanged file if the bot gets invoked on demand several times in a row.

Errors you'll actually hit in production

503 model_disabled — the configured model has been switched off in the catalog (not a 404 and not model_retired); take a replacement from the current GET https://api.zukko.pro/v1/models list and log the fact for a later pipeline update. 503 provider_unavailable — the model is temporarily unavailable at the provider; for both cases it's worth keeping a fallback to another model, e.g. gpt-5.6-sol via /v1/chat/completions, so review doesn't fully block a merge. 422 idempotency_conflict shows up when the same Idempotency-Key is sent with a different request body — usually a bug in key generation (like forgetting to include the PR number), not a server problem.

Put together: a dedicated capped key for the bot, idempotency on every call, count_tokens before large diffs, and logging X-Zukko-Cost-Micros — that's enough to run code review on Claude in production for months without surprises on the bill.

zukko

Pay 25×
less

for Claude Code & ChatGPT

One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.

Get API keyDocumentation

api.zukko.pro · fetch_*

Automated code review with Claude API in CI/CD · Zukko