ИИ-ревью кода в GitLab CI: комментарий к merge request через API

Job в GitLab CI получает diff merge request, отправляет его в Claude через Zukko и оставляет в MR комментарий с замечаниями, обновляя его при каждом пуше.

5 мин · · разработка · автоматизация

ИИ code review в GitLab CI не заменяет ревьюера, но снимает с него часть рутины: необработанные ошибки, забытые проверки на None, подозрительные SQL-запросы модель замечает до того, как MR откроет человек. Ниже — job для .gitlab-ci.yml и скрипт на Python: он берёт diff merge request, отправляет его в Claude через Zukko и оставляет в MR один комментарий, который обновляется при каждом новом пуше.

Как это работает

  1. На событие merge request GitLab запускает pipeline с job ai-review.
  2. Скрипт считает diff между базой MR (CI_MERGE_REQUEST_DIFF_BASE_SHA) и текущим коммитом.
  3. Diff уходит в модель через OpenAI-совместимый API Zukko (https://api.zukko.pro/v1).
  4. Ответ публикуется комментарием через Notes API GitLab: POST /projects/:id/merge_requests/:merge_request_iid/notes. Если комментарий от прошлого запуска уже есть, он обновляется через PUT .../notes/:note_id, чтобы не засорять обсуждение.

Все нужные идентификаторы GitLab передаёт в job сам, через предопределённые переменные:

ПеременнаяЧто содержит
CI_API_V4_URLадрес API GitLab v4 (работает и на self-hosted)
CI_PROJECT_IDID проекта
CI_MERGE_REQUEST_IIDномер MR внутри проекта
CI_MERGE_REQUEST_DIFF_BASE_SHAбазовый коммит diff merge request
CI_MERGE_REQUEST_TITLEзаголовок MR
CI_MERGE_REQUEST_DRAFTtrue, если MR — черновик (GitLab 17.10+)

Переменные CI_MERGE_REQUEST_* доступны только в pipeline для merge request, поэтому job запускается по правилу CI_PIPELINE_SOURCE == "merge_request_event".

Токены и переменные CI/CD

Понадобятся два секрета.

Токен GitLab для комментариев. Удобнее всего — project access token (настройки проекта → Access tokens) со scope api и ролью, которой разрешено комментировать MR, например Developer. Комментарии будут идти от бот-пользователя проекта, а не от вашего аккаунта.

Ключ Zukko вида fetch_... — создаётся в кабинете. Для CI заведите отдельный ключ с дневным и месячным лимитом в долларах и списком разрешённых моделей: тогда ошибка в pipeline не потратит больше лимита. Подробнее — в разделе лимиты ключей.

Оба значения добавьте в Settings → CI/CD → Variables под именами GITLAB_REVIEW_TOKEN и ZUKKO_API_KEY, с флагом Masked, чтобы они не попадали в логи. С флагом Protected переменная видна только pipeline защищённых веток, а ветки MR обычно не защищены, — тогда job её не получит. Учтите обратную сторону: незащищённую переменную может прочитать любой, кто может пушить ветки в проект, поэтому выдавайте токенам минимальные права.

Ещё один нюанс: pipeline для MR из форка по умолчанию запускаются в проекте-форке, и переменные основного проекта туда не попадают.

Job в .gitlab-ci.yml

bash
ai-review:
  stage: test
  image: python:3.12
  variables:
    GIT_DEPTH: 0
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_DRAFT != "true"
  script:
    - pip install --quiet openai requests
    - python ci/ai_review.py
  allow_failure: true
  timeout: 10m

Что здесь важно:

  • GIT_DEPTH: 0 — полная история, иначе базового коммита MR может не оказаться в клоне, и git diff упадёт.
  • Образ python:3.12 (не slim) уже содержит git.
  • allow_failure: true — если API недоступен или кончился баланс, MR всё равно можно влить.
  • Черновики MR пропускаются, пока автор не снимет статус Draft.

Скрипт ai_review.py

Положите файл в ci/ai_review.py:

bash
import os
import subprocess

import requests
from openai import OpenAI

API = os.environ["CI_API_V4_URL"]
PROJECT = os.environ["CI_PROJECT_ID"]
MR_IID = os.environ["CI_MERGE_REQUEST_IID"]
BASE_SHA = os.environ["CI_MERGE_REQUEST_DIFF_BASE_SHA"]
GITLAB = {"PRIVATE-TOKEN": os.environ["GITLAB_REVIEW_TOKEN"]}

MODEL = os.getenv("REVIEW_MODEL", "claude-sonnet-5")
MAX_DIFF_CHARS = int(os.getenv("REVIEW_MAX_DIFF_CHARS", "60000"))

Пояснения к решениям:

  • Исключения. Lock-файлы и минифицированный код почти никогда не нужно ревьюить, а токенов они съедают много. Список EXCLUDE — обычные pathspec git, дополняйте под свой проект.
  • Ограничение размера. MAX_DIFF_CHARS защищает от огромных MR (массовые переименования, сгенерированный код). Для MR, которые всё равно не влезают, лучше разбивать diff по файлам и отправлять несколько запросов.
  • Один комментарий. Метка <!-- ai-review --> в начале комментария невидима в интерфейсе, но по ней скрипт находит свой прошлый комментарий и обновляет его.
  • Ошибки. Если Zukko вернёт ошибку (401 — ключ, 402 — баланс, 403 — модель не разрешена для ключа, 429 — частота запросов), SDK поднимет исключение, job упадёт с понятным текстом в логе, а allow_failure не даст заблокировать MR.

Стоимость и выбор модели

Каждый пуш в ветку MR запускает ревью заново, так что считать стоит по числу пушей. Пример: diff на 8 000 входных токенов и ответ на 800 выходных на claude-sonnet-5 ($0.30 / $0.30 за 1M токенов в Zukko):

(8 000 + 800) × $0.30 / 1 000 000 = $0.00264 за запуск. При 200 запусках в месяц — 200 × $0.00264 = $0.528.

На glm-5.3-flash ($0.04 / $0.04) тот же объём: 200 × 8 800 × $0.04 / 1 000 000 = $0.0704 в месяц. Модель меняется переменной REVIEW_MODEL без правки скрипта; актуальный список — GET https://api.zukko.pro/v1/models или страница «Модели» в кабинете. Какая модель находит больше настоящих проблем в вашем коде, проверьте на своих задачах: прогоните 10–15 уже закрытых MR с известными багами через две модели и сравните, что каждая нашла.

Частые вопросы

Job падает с «fatal: bad object» на git diff

В клоне нет базового коммита MR. Проверьте, что в job задано GIT_DEPTH: 0, а в настройках проекта (CI/CD → General pipelines) не стоит маленькая глубина клонирования, которая переопределяет значение.

Комментарий не появляется, в логе 401 или 403 от GitLab

Токен не задан, истёк или у него нет scope api. Для project access token проверьте и роль: у Guest может не хватить прав комментировать MR. Если переменная помечена Protected, в pipeline незащищённой ветки её просто нет.

Можно ли оставлять замечания на конкретных строках?

Notes API создаёт общий комментарий к MR. Для комментариев к строкам в GitLab есть Discussions API с указанием позиции в diff — это сложнее: нужно сопоставлять номера строк из ответа модели с версиями diff. Начните с общего комментария.

Не опасно ли отправлять код во внешний API?

Это решение вашей компании: diff уходит модели через Zukko. Если в репозитории бывают секреты, добавьте в EXCLUDE файлы конфигурации и .env, а лучше — не храните секреты в репозитории вовсе. Про форматы API и авторизацию — в документации Zukko.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.

ИИ code review в GitLab CI через API нейросети · Zukko