Code review нейросетью в GitHub Actions: workflow с curl и gh CLI
Один YAML-файл — и каждый пул-реквест получает комментарий с замечаниями от модели. Разбираем workflow по шагам, права токена, ограничения для форков и стоимость одного ревью.
5 мин · · автоматизация · разработка
Ревьюер-человек устаёт, пропускает очевидное в длинных диффах и не всегда доступен в пятницу вечером. Code review нейросетью в GitHub Actions не заменяет его, но снимает первый слой: забытые проверки на None, утечки секретов в логи, опечатки в условиях. Ниже — готовый workflow на curl и gh, который оставляет комментарий к каждому пул-реквесту, и расчёт, сколько стоит одно ревью.
Как устроен workflow
Три шага на раннере GitHub:
- Получить diff пул-реквеста командой
gh pr diff. - Отправить diff модели через OpenAI-совместимый эндпоинт
https://api.zukko.pro/v1/chat/completionsс ключом из секретов репозитория. - Опубликовать ответ комментарием через
gh pr comment.
GitHub CLI предустановлен на всех раннерах, которые предоставляет GitHub; по документации GitHub, для каждого шага с gh нужно задать переменную окружения GH_TOKEN. Утилиты curl и jq на ubuntu-latest тоже есть, так что ставить ничего не нужно.
Шаг 0. Ключ и секрет
- Создайте отдельный ключ в кабинете на странице API-ключи. Задайте ему дневной лимит в долларах и разрешите только модель, которую будет звать workflow, — тогда ошибка в YAML не обернётся неожиданным счётом. Подробнее — в разделе о лимитах ключа.
- В репозитории откройте Settings → Secrets and variables → Actions и добавьте секрет
ZUKKO_API_KEYсо значениемfetch_....
Code review нейросетью в GitHub Actions: готовый workflow
Сохраните файл как .github/workflows/ai-review.yml:
name: AI code review
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
jobs:
review:
# секреты не передаются в запуски из форков — такие PR пропускаем
if: github.event.pull_request.head.repo.full_name == github.repositoryЧто здесь важно
- Триггер. По документации GitHub, событие
pull_requestпо умолчанию срабатывает наopened,synchronizeиreopened; здесь они перечислены явно, чтобы было видно, что ревью запускается и на каждый новый пуш в ветку. - Права.
pull-requests: writeнужен, чтобы оставить комментарий;contents: read— минимальный доступ к коду. Остальные права токена при явном блокеpermissionsотключены. - Без checkout.
gh pr diffберёт diff через API GitHub, поэтому клонировать репозиторий не нужно; флаг--repoуказывает, в каком репозитории искать PR. - Обрезка диффа.
head -c 150000ограничивает размер запроса и стоимость. Огромные PR модель всё равно читает хуже — лучше дробить их. В свежих версияхghуgh pr diffесть флаг--excludeдля исключения файлов по шаблону, например--exclude '*.lock'. - JSON через jq.
--rawfileчитает diff как строку и экранирует кавычки и переносы — собирать JSON склейкой строк в bash ненадёжно. - Ошибки.
curl --fail-with-bodyзавершает шаг с ошибкой при HTTP-статусе 400 и выше, и в логе видно тело ответа: 401 — неверный ключ, 402 — не хватает баланса, 403model_not_allowed_for_key— модель не разрешена ключу.
Пулл-реквесты из форков
По документации GitHub, в запуски из форков не передаются секреты (кроме GITHUB_TOKEN), а сам GITHUB_TOKEN получает только права на чтение. Поэтому условие if: в примере пропускает такие PR. Событие pull_request_target даёт доступ к секретам, но запускать с ним код или данные из чужого форка рискованно — для публичных репозиториев безопаснее запускать ревью вручную после просмотра PR.
Вариант на Python
Если промпт сложный или нужно разбить большой diff на части, шаг «Ask the model» удобнее написать на Python:
import os
from pathlib import Path
from openai import OpenAI
client = OpenAI(
base_url="https://api.zukko.pro/v1",
api_key=os.environ["ZUKKO_API_KEY"],
)
diff = Path("pr_trimmed.diff").read_text(encoding="utf-8", errors="replace")
response = client.chat.completions.create(
model=os.environ.get("MODEL", "claude-sonnet-5"),
max_tokens=2000,
messages=[
{"role": "system", "content": "Ты опытный ревьюер кода. Ищи ошибки и проблемы "
"безопасности, не комментируй стиль."},
{"role": "user", "content": f"Diff пул-реквеста:\n\n{diff}"},
],
)
Path("review.md").write_text(response.choices[0].message.content, encoding="utf-8")
print("usage:", response.usage)В workflow замените шаг на pip install openai и python review.py. Подробнее о параметрах запроса — в справочнике chat/completions.
Сколько стоит одно ревью
Формула:
стоимость = входные_токены × цена_входа / 1 000 000
+ выходные_токены × цена_выхода / 1 000 000
Входные токены — это diff плюс системный промпт; точное число workflow печатает в логе шагом jq '.usage'. Посмотрите его на нескольких реальных PR. Для примера возьмём 20 000 входных и 1 500 выходных токенов (цены на 6 октября 2026, $ за 1M токенов):
| Модель | Вход | Выход | Итого за ревью |
|---|---|---|---|
deepseek-v4.1-flash | 20 000 × 0.013 / 1M = $0.00026 | 1 500 × 0.038 / 1M = $0.000057 | $0.000317 |
claude-sonnet-5 | 20 000 × 0.2 / 1M = $0.004 | 1 500 × 1.013 / 1M = $0.0015195 | $0.0055195 |
gpt-6.1-sol | 20 000 × 0.27 / 1M = $0.0054 | 1 500 × 1.32 / 1M = $0.00198 | $0.00738 |
claude-opus-5-5 | 20 000 × 0.4 / 1M = $0.008 | 1 500 × 2 / 1M = $0.003 | $0.011 |
Учтите, что synchronize запускает ревью на каждый пуш. Если в месяц набирается 300 запусков на claude-sonnet-5, это 300 × $0.0055195 ≈ $1.66. Если модель рассуждает, у вендоров токены рассуждений обычно учитываются как выходные и увеличивают вторую часть суммы — смотрите фактические completion_tokens в логе. Подробнее — в статье о стоимости рассуждений.
Какая модель находит больше реальных проблем в вашем коде, проверьте сами: прогоните 10–15 закрытых PR с известными багами через 2–3 модели и сравните. Актуальный список и цены — GET https://api.zukko.pro/v1/models или страница «Модели» в кабинете. Похожий сценарий на Anthropic-формате разобран в статье об автоматическом код-ревью через API.
Частые вопросы
Почему workflow не запускается для PR из форка?
Так задумано: в запуски из форков GitHub не передаёт секреты, и ключа ZUKKO_API_KEY там не будет. Условие if: пропускает такие PR, чтобы шаг не падал с ошибкой 401.
Как не плодить новый комментарий на каждый пуш?
У gh pr comment есть флаги --edit-last и --create-if-none: первый редактирует последний комментарий текущего пользователя, второй создаёт комментарий, если редактировать нечего. Проверьте, что версия gh на раннере их поддерживает.
Можно ли оставлять комментарии к конкретным строкам?
Да, через REST API ревью пул-реквестов (например, командой gh api), но тогда нужно сопоставлять строки диффа с позициями в файле. Начните с общего комментария — его проще поддерживать.
Уходит ли код за пределы GitHub?
Да: diff отправляется модели через шлюз. Если политика компании это ограничивает, согласуйте использование заранее или исключайте чувствительные файлы из диффа.
zukko
Платите в 25×
меньше
за Claude Code & ChatGPT
Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.
Похожие статьи
- Перевод субтитров нейросетью: SRT без сбитых таймкодовГлавный риск при переводе SRT нейросетью — сломанная структура файла. Показываем схему, где таймкоды не попадают к модели, и скрипт на Python с проверкой.
- Редактирование текстов нейросетью: корректура, стиль и сокращениеРазные виды редактуры требуют разных промптов. Показываем формулировки для корректуры, стиля и сокращения, как получать правки с объяснением и не терять авторский голос.
- Нейросеть для написания скриптов: Bash, PowerShell и PythonМодель быстро пишет скрипты для рутины, но запускать их вслепую нельзя. Показываем, как ставить задачу, требовать безопасный режим dry-run и проверять результат до запуска.
- Dify в Docker: установка, первичная настройка и подключение моделейСтавим Dify через Docker Compose, создаём администратора и подключаем модели Claude, GPT и DeepSeek по одному ключу. Отдельно — что делать, если контейнеры не стартуют.