Ревью пул-реквестов — узкое место современной разработки. Сеньоры тратят часы, листая диффы, где 90% — бойлерплейт, пропускают те самые 10%, которые имеют значение, и всё равно мерджат тонкие баги. В 2026 году нет повода продолжать делать это руками. Claude Sonnet 4.5 — сильнейшая публично доступная модель для кода, и в паре с GitHub API она даёт вам собственного ревьюера, который работает на каждом PR, стоит копейки и не устаёт.
В этом гайде мы соберём такого бота — не игрушку, а реальный продакшн. Будем использовать Claude Sonnet 4.5 через Claudexia (https://api.claudexia.tech/v1): тот же API Anthropic, но дешевле и без проблем с лимитами.
Почему именно Claude для код-ревью
Три причины, по убыванию важности:
- Способность к коду. Sonnet 4.5 на вершине SWE-bench Verified и читает диффы как сеньор — отслеживает типы между файлами, замечает пропущенную обработку ошибок, ловит off-by-one, которые линтер не видит. GPT-5 и Gemini 3 близко, но именно для ревью (где ценится консервативность и обоснование) Claude выигрывает по соотношению сигнал/шум.
- Надёжный структурированный вывод. Claude следует JSON-схемам в
tool_useпрактически со 100% соблюдением. Это критично: вывод бота должен парситься чисто каждый раз — один кривой JSON на тысячу запусков, и вас будят ночью. - Длинный контекст + кэширование промптов. 200K токенов позволяют засунуть в системный промпт весь стайл-гайд репо, архитектурные заметки и недавние коммиты. Кэширование делает это практически бесплатным после первого вызова. См. наш разбор цен Claude API с экономикой кэша.
Сравнение с хостед-сервисами вроде CodeRabbit и Greptile: они нормальные, но вы отдаёте им свой исходный код, платите $30–$100 на разработчика в месяц и принимаете тот промпт, который они написали. Свой бот стоит $0.05–$0.30 за PR, работает на ваших промптах, и код не покидает ваш CI.
Архитектура
Полный пайплайн — четыре блока:
PR открыт/обновлён в GitHub
↓
Срабатывает GitHub Action
↓
Тянем дифф через GitHub API
↓
Шлём в Claude Sonnet 4.5 (через Claudexia)
↓
Парсим JSON-ответ
↓
Постим комменты ревью через GitHub API
Всё. Никакой векторной БД, эмбеддингов, агентного цикла. Дифф маленький (обычно <50K токенов), Claude читает его один раз и выдаёт структурированные комменты. Что-то сложнее — оверинжиниринг, пока у вас нет данных, говорящих обратное.
Промпт
Промпт — 80% работы. Вот шаблон, который у нас отработал на Go, TypeScript и Python:
Ты — senior staff engineer, ревьюишь pull request. Твоя задача — ловить
баги, проблемы безопасности и ошибки дизайна, а НЕ следить за стилем
(для этого есть линтеры) и НЕ хвалить хороший код.
SCOPE:
- Комментируй только строки, изменённые в этом диффе.
- Не комментируй неизменённые контекстные строки.
- Не требуй изменений, которые не можешь обосновать конкретным сценарием
отказа.
СЕРЬЁЗНОСТЬ:
- blocker: приведёт к продакшн-инциденту, потере данных, бреши в
безопасности или очевидному багу корректности. Чинить до мерджа.
- warning: реальная проблема, но не блокер. Гонки под маловероятной
нагрузкой, отсутствующие error-пути, плохое именование, которое
выстрелит позже.
- nit: мелкая придирка. Используй экономно — максимум 2 на PR.
ВЫВОД:
Вызови инструмент `post_review` со списком комментариев в JSON. Каждый
комментарий должен содержать:
- file: путь относительно корня репо
- line: номер строки в новом файле (после изменения)
- severity: "blocker" | "warning" | "nit"
- comment: 1–3 предложения. Сначала проблема, затем как чинить.
- justification: почему это важно. Если не можешь это сформулировать —
выкини коммент.
Если PR выглядит нормально — верни пустой список. Не выдумывай проблемы.
КОНТЕКСТ РЕПО:
{кэшированный системный блок: стайл-гайд, заметки по архитектуре,
недавние решения}
ДИФФ:
{собственно дифф}
Две неочевидные вещи делают это рабочим:
Поле justification — это форсинг-функция против ложных срабатываний. Claude (и любая другая модель) будет галлюцинировать проблемы, если ей позволить. Требование письменного обоснования режет шум на ~60% в нашем внутреннем эвале — модель сама отсекает слабые комменты до того, как их выдать.
tool_use вместо «ответь JSON-ом». Описав post_review как инструмент со строгой схемой ввода, вы заставляете API само отбраковывать кривой вывод. Никаких функций починки JSON писать не надо.
Описание инструмента
const postReviewTool = {
name: "post_review",
description: "Опубликовать комменты ревью на PR. Передай пустой массив, если проблем нет.",
input_schema: {
type: "object",
properties: {
comments: {
type: "array",
items: {
type: "object",
properties: {
file: { type: "string" },
line: { type: "integer" },
severity: {
type: "string",
enum: ["blocker", "warning", "nit"]
},
comment: { type: "string" },
justification: { type: "string" }
},
required: ["file", "line", "severity", "comment", "justification"]
}
}
},
required: ["comments"]
}
};
Установите tool_choice: { type: "tool", name: "post_review" }, чтобы заставить модель вызвать его. Никакого текстового ответа, никаких утечек.
Бот (TypeScript)
import Anthropic from "@anthropic-ai/sdk";
import { Octokit } from "@octokit/rest";
const claude = new Anthropic({
apiKey: process.env.CLAUDEXIA_API_KEY,
baseURL: "https://api.claudexia.tech/v1",
});
const gh = new Octokit({ auth: process.env.GITHUB_TOKEN });
async function reviewPR(owner: string, repo: string, pull_number: number) {
// 1. Тянем дифф
const { data: files } = await gh.pulls.listFiles({
owner, repo, pull_number, per_page: 100,
});
// 2. Бьём по файлам — параллелим для больших PR
const reviews = await Promise.all(
files
.filter(f => f.patch && f.status !== "removed")
.map(file => reviewFile(file))
);
// 3. Сплющиваем + дедуп по (file, line, severity)
const all = reviews.flat();
const seen = new Set<string>();
const unique = all.filter(c => {
const k = `${c.file}:${c.line}:${c.severity}`;
if (seen.has(k)) return false;
seen.add(k);
return true;
});
// 4. Постим одним ревью
await gh.pulls.createReview({
owner, repo, pull_number,
event: unique.some(c => c.severity === "blocker")
? "REQUEST_CHANGES"
: "COMMENT",
comments: unique.map(c => ({
path: c.file,
line: c.line,
body: `**[${c.severity}]** ${c.comment}\n\n_${c.justification}_`,
})),
});
}
async function reviewFile(file: any) {
const response = await claude.messages.create({
model: "claude-sonnet-4.5",
max_tokens: 4096,
system: [
{
type: "text",
text: REPO_CONTEXT, // стайл-гайд, архитектура
cache_control: { type: "ephemeral" },
},
{
type: "text",
text: REVIEW_INSTRUCTIONS, // промпт выше
},
],
tools: [postReviewTool],
tool_choice: { type: "tool", name: "post_review" },
messages: [
{
role: "user",
content: `Файл: ${file.filename}\n\nДифф:\n\`\`\`diff\n${file.patch}\n\`\`\``,
},
],
});
const toolUse = response.content.find(b => b.type === "tool_use");
return toolUse ? (toolUse.input as any).comments : [];
}
Обратите внимание на cache_control на блоке контекста репо. Это и есть кэширование промптов — первый PR платит за эти токены полную цену, каждый следующий в течение 5 минут платит 10%. На активном репо это режет стоимость на 70%+.
Большие диффы
PR на 5000 строк взорвёт ваш бюджет токенов, если слать одним куском. Стратегия чанкования выше (один вызов Claude на файл) решает это естественным образом:
- Вызовы по файлам идут параллельно — wall time остаётся ~3–5 секунд даже для PR из 30 файлов.
- Каждый вызов получает сфокусированный контекст — Claude ревьюит
auth.tsбезpackage-lock.jsonпод боком. - Дедуп между вызовами — иногда Claude помечает один и тот же паттерн в двух файлах; ключ
(file, line, severity)убирает дубли.
Для реально огромных PR (>100 файлов) добавьте префильтр, который выкидывает лок-файлы, сгенерированный код и vendor/. Большинство «огромных PR» сжимаются до 10 ревьюабельных файлов после этого.
Настройка GitHub Actions
name: Claude Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm ci
- run: npx tsx scripts/review.ts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CLAUDEXIA_API_KEY: ${{ secrets.CLAUDEXIA_API_KEY }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO_OWNER: ${{ github.repository_owner }}
REPO_NAME: ${{ github.event.repository.name }}
Кладёте ключ Claudexia в секреты репо, коммитите YAML — и оно работает.
Стоимость на PR
Реальные цифры с нашего деплоя по 200 PR:
| Размер PR | Токены вход | Токены выход | Стоимость (с кэшем) |
|---|---|---|---|
| Маленький (<10 файлов) | ~8K | ~500 | $0.04 |
| Средний (10–30) | ~25K | ~1.5K | $0.12 |
| Большой (30–80) | ~70K | ~3K | $0.28 |
Это с ценами Claude Sonnet 4.5 на Claudexia и активным кэшированием. Команда из 50 разработчиков на 100 PR в день — примерно $300 в месяц. Меньше одной лицензии CodeRabbit Pro.
Свой бот vs SaaS-ревьюеры
| Параметр | Ваш бот (Claude + Claudexia) | CodeRabbit / Greptile |
|---|---|---|
| Стоимость | $0.05–$0.30 за PR | $30–$100 на разраба/мес |
| Контроль промпта | Полный | Никакого |
| Где живут данные | Ваш CI | Их сервера |
| Кастомизация | Что напромптите | Их фичи |
| Время на запуск | Один день | Один час |
| Поддержка | Ваша | Их |
Размен реальный: SaaS быстрее ставится и вы его не поддерживаете. Своё даёт контроль, дешевле в пересчёте на PR на масштабе и возможность закодировать реальный вкус вашей команды.
Итог
Это можно зашипить за день. Все компоненты — Claude Sonnet 4.5 через Claudexia, GitHub Actions, Octokit SDK — стабильны, хорошо задокументированы и дешёвы. Единственная сложная часть — промпт, и шаблон выше покрывает 90% того, что нужно.
Начните с простого: один промпт, по файлу за раз, blocker/warning/nit. Прогоните по 20 последним замердженным PR и почитайте вывод. Итерация промпта пройдёт два раза, и к концу недели у вас будет нечто лучшее, чем большинство сервисов по $50 в месяц.