Решение проблем
Чаще всего «бот молчит» — это один из четырёх разрывов в цепочке GitLab → вебхук → бот → LLM. Запустите скрипт диагностики — он найдёт, какой именно, — или пройдите проверки ниже руками. Строки, которые бот пишет в MR/PR, приведены здесь в русском варианте: он включается ключом language: ru в .reviewgate/config.yml, по умолчанию бот отвечает по-английски.
Скрипт диагностики
diagnose.sh запускается на хосте рядом с docker-compose и .env бота и за пару секунд проверяет всю цепочку: переменные окружения, здоровье бота, токен GitLab, приём вебхука, Redis, ключ LLM и прокси. Заодно печатает версию вашего бота — её просят приложить к обращению в поддержку.
curl -O https://reviewgate.dev/diagnose.ru.sh
less diagnose.ru.sh # read it — it is short and holds no surprises
bash diagnose.ru.sh.env (построчно, не выполняя его) и шлёт безвредные запросы: GET к вашему GitLab и боту, «пинг» вебхука, который бот игнорирует (ревью не запускается), и GET /v1/models к LLM — это не тратит токены. Дифф и код никуда не уходят.Параметры под нестандартный контур:
# the bot is behind a reverse proxy, or the port is not on localhost:
bash diagnose.ru.sh --bot-url https://bot.acme.ru
# and check that the webhook is configured in the project:
bash diagnose.ru.sh --project 42Ревью не появилось в MR/PR
Пять проверок по порядку цепочки — или просто запустите diagnose.sh:
- Бот жив.
curl http://localhost:3000/api/healthдолжен вернутьstatus:ok. Если нет — см. раздел «Бот не отвечает» ниже. - Вебхук доставляется.GitLab → проект → Settings → Webhooks → ваш хук → Recent events. Видно код ответа последней доставки:
202— бот принял;401— не совпал секрет; ошибка соединения — GitLab не достучался. - GitLab достучится до бота.Для бота во внутренней сети нужно разрешение — см. «Локальная сеть» ниже.
- Секрет совпадает.
GITLAB_WEBHOOK_SECRET↔ поле Secret token в вебхуке — см. «Вебхук отклонён» ниже. - LLM отвечает.Ключ валиден и, если вашей сети нужен прокси, он работает — см. «Ревью пустое» ниже.
202 и когда событие принято, и когда оно штатно пропущено: правка описания или лейбла, аппрув, мерж, системная заметка — новых коммитов там нет. Причина всегда пишется в лог строкой ⏭ Webhook skipped, а в теле ответа возвращается queued: false. Если же GitLab показывает в Recent events код 500 — ищите в логе записи уровня error: такие доставки GitLab не повторяет, и событие теряется.⏭ Skipped: head … is already reviewed. Чтобы получить новое ревью, нужен новый коммит или смена целевой ветки. Прогон, который не дошёл до конца, вершину отревьюенной не помечает — повтор после сбоя проходит как обычно.GitLab не достучится до бота (локальная сеть)
Причина №1, и её легко пропустить: self-hosted GitLab по умолчанию запрещает вебхуки во внутреннюю сеть. Доставки просто не происходит — в Recent events ошибка соединения или пусто.
Бот не отвечает или не стартует
Проверьте здоровье и логи:
curl http://localhost:3000/api/health
# {"status":"ok","ts":...,"version":"0.1.x"}docker compose logs --tail=50 appЕсли /api/health молчит — контейнер не поднялся либо порт не проброшен наружу. Когда бот стоит за reverse-proxy, у diagnose.sh укажите --bot-url с его адресом.
«Некорректная конфигурация окружения»
Бот проверяет переменные окружения на старте и падает сразу с указанием, чего не хватает. Безусловно обязателен только REDIS_HOST. Дальше нужен хотя бы один настроенный вендор: GitLab — GITLAB_BASE_URL + GITLAB_TOKEN + GITLAB_WEBHOOK_SECRET, GitHub — GITHUB_WEBHOOK_SECRET + (GITHUB_APP_ID и GITHUB_APP_PRIVATE_KEY_B64 либо GITHUB_TOKEN); у обоих секретов вебхука минимум 8 символов. Группа вендора становится обязательной целиком, как только задана хотя бы одна её переменная: одинокий GITLAB_TIMEOUT_MS из шаблона потребует и остальных трёх GitLab-переменных. Полный справочник — «Установка и запуск».
Образ не скачивается
Канонический адрес образа — registry.reviewgate.dev/reviewgate-bot (наш реестр, pull анонимный). Если он недоступен — тот же образ бит-в-бит лежит на зеркале Docker Hub: замените в docker-compose.yml строку image: на novohudonossor/reviewgate-bot:<тот же тег>.
Если недоступен и Docker Hub — он уже закрывал доступ целым регионам, и гарантий, что это не повторится, нет, — подключите pull-through-зеркало. Оно проксирует только образы Docker Hub, поэтому работает в паре с предыдущим шагом: в image: должно стоять имя с Docker Hub (novohudonossor/…), его менять уже не нужно:
# /etc/docker/daemon.json (create it if the file is missing;
# in an existing one, ADD the key without wiping the other settings)
{
"registry-mirrors": ["https://dh-mirror.gitverse.ru"]
}
# then restart Docker (it restarts every container on the host —
# pick a quiet moment):
sudo systemctl restart dockerGitLab недоступен или токен не принят
diagnose.sh шаг 3 бьёт GET /api/v4/user. Ошибка соединения — неверный GITLAB_BASE_URL (он должен быть без /api/v4 и хвостового слэша) или сеть. Код 401/403 — проблема с токеном.
Права токена
GITLAB_TOKEN — Project или Personal Access Token со scope api, роль не ниже Developer (чтобы оставлять discussion-комментарии). Протухший или урезанный токен даёт 401/403.
Вебхук отклонён: 401
В Recent events у доставки код 401 — бот получил вебхук, но секрет не совпал. Сверьте поле Secret token в настройке вебхука со значением GITLAB_WEBHOOK_SECRET в .env.
.env поправили, а контейнер не перезапустили — бот держит старый секрет. Перезапустите: docker compose up -d app.Вебхук отклонён: 413 или 400
В Recent events у доставки код 413 — тело запроса больше лимита бота, чаще всего это событие комментария, куда вставили большой отчёт или лог. Код 400 в той же графе значит, что тело не удалось прочитать: битый JSON или оборванное соединение.
В обоих случаях событие потеряно — вендор такие доставки сам не повторяет, и по ним не будет ни ревью, ни ответа в треде. В логе бота отказ виден отдельной строкой Тело запроса отклонено с типом события и заявленным размером — по ней вы найдёте доставку в списке недавних и при необходимости отправите её заново: у GitLab это кнопка Resend, у GitHub — Redeliver.
GitHub: бот молчит, а доставки вебхука успешны
В App: Advanced → Recent Deliveries видно 202, но в PR ничего не появляется — почти всегда дело в наборе событий, а не в боте.
- Нужного события нет в подписке. Вопрос под сводкой приходит событием Issue comment, и в списке событий App оно появляется только после выдачи права Issues (read). Ответы в тредах замечаний — событие Pull request review comment.
- Права выданы, но не приняты инсталляцией. Галочка меняет права только у самого App; каждая инсталляция принимает их отдельно (Configure → «Accept new permissions»). До принятия GitHub шлёт старый набор событий, и это выглядит как молчание без ошибок.
- Ответы вообще выключены. Reply mode по умолчанию выключен — включается
reply.enabledв.reviewgate/config.yml(илиREPLY_ENABLEDв окружении бота). В логе бота это видно прямой строкой «Ответ пропущен: reply mode выключен». - Под сводкой бот отвечает только на упоминание своей учётки: лента PR у GitHub не тредирована, и без этого правила бот вклинивался бы в разговоры людей.
Быстрая сверка фактического состояния: GET /app (что у App) против GET /repos/{owner}/{repo}/installation (что реально действует на инсталляции) — расхождение permissions/events и есть ответ.
Redis недоступен
Очередь обязательна: вебхук только кладёт задачу, всю работу делает воркер. Зелёный /api/health ничего не говорит о Redis — ответ собирается, не заглядывая в очередь, а клиент Redis подключается лениво, поэтому бот поднимается и отвечает status:ok при лежащем Redis. Проверяйте сам Redis: docker compose ps redis — и лог бота, куда выписывается ошибка соединения с REDIS_HOST/REDIS_PORT.
В логе: «Джоба … признана зависшей»
Очередь считает задачу зависшей, когда до Redis перестаёт доходить продление блокировки — обычно из-за просадки самого Redis или перегруженного воркера, а не из-за длительности ревью (её продление переживает). Задача выдаётся повторно, но второй платный прогон почти всегда не случается: дубль ждёт, пока закончится первый (до 10 минут), и затем видит, что вершина уже отревьюена — в логе строка ⏭ Пропуск, в метриках статус repeat_head. Задвоиться прогон может, если первый идёт дольше отсрочек: тогда рядом в логе будет запись об исчерпанных отсрочках. Единичные записи можно игнорировать; регулярные — повод посмотреть доступность Redis и REVIEW_CONCURRENCY.
Ревью пустое или комментарий про ошибку ключа
Бот публикует находки, но если LLM недоступна — в сводку попадёт уведомление о причине (BYOK: бот не угадывает за вас). diagnose.sh шаг 6 проверяет ключ через GET /v1/models, не тратя токены: 401/403 — ключ неверный или нет кредитов; ошибка соединения — см. «Прокси к LLM» ниже.
Прокси к LLM
Если ваша сеть не достаёт до провайдера напрямую, нужен LLM_PROXY (слепой CONNECT-туннель; TLS до провайдера остаётся end-to-end, прокси видит только хост, не код). Если ключ задан, а соединения нет — проверьте, что прокси жив и креды верны. Локальной LLM (Ollama/vLLM) прокси не нужен — как и провайдеру, до которого сеть и так достаёт; подробнее в разделе «LLM и свой ключ». Для OpenAI-совместимых провайдеров (DeepSeek, OpenRouter, Ollama, vLLM — не Anthropic: там соединение повторяет сам SDK) стрим, который провайдер оборвал на середине ответа (в логе — stream interrupted … retrying 1/1), повторяется один раз сам, прежде чем считаться сетевой ошибкой; повтор виден и в диагностике прогона, и в usage.calls[].retries отчёта — расход оборванной попытки в токенах не учтён.
Ревью не выполнено: контекст не влез в окно модели
Бот пишет в MR/PR, что контекст не помещается в окно модели, а в логах — prompt is too long. Обычно причина в том, что в изменения попал очень большой или сгенерированный код: у него объём в разы больше рукописного, а ревьюить его незачем. Чаще всего это всплывает при включённом llm.full_file_context — бот берёт изменённые файлы целиком.
Что помогает, по порядку:
- добавить генерируемое в
ignoreв config.yml — например**/*.g.dart,**/*.freezed.dart(Flutter),**/*.generated.ts, снапшоты, бандлы, вендорные каталоги; - выключить
llm.full_file_context— тогда бот смотрит только дифф; - разбить MR/PR на части поменьше;
- на модели с небольшим окном (обычно локальной LLM) — уменьшить
LLM_DIFF_CHAR_BUDGETиLLM_MAX_TOKENS: в лимит окна входит и запрошенный размер ответа.
Ретраев на этой ошибке нет: результат не изменился бы — нужна правка конфига. Если включён блок 🔬 диагностики, то в следующем успешном ревью строка «Контекст модели» покажет, сколько файлов не влезло в бюджет.
Ревью не выполнено: ответ не уместился в лимит выхода
Зеркальная ситуация: в окно модели всё влезло, но ответ модели упёрся в потолок LLM_MAX_TOKENS (в логах — truncated by max_tokens или finish_reason=length). Обычно так бывает на очень больших MR и PR: у reasoning-моделей внутренние рассуждения расходуют тот же лимит, что и сам ответ, плюс объём находок растёт вместе с диффом.
Бот сначала лечит это сам: на моделях Anthropic повторяет запрос с сокращёнными рассуждениями (на OpenAI-совместимых повтор не делается — при temperature: 0 он обрезался бы точно так же). Если не помогло — пишет уведомление в MR/PR и не тратит деньги на слепые ретраи: каждый повтор оплачивал бы весь вход заново. Что помогает:
- поднять
LLM_MAX_TOKENSв окружении бота — в пределах потолка выхода вашей модели: он сильно различается между провайдерами (от единиц до десятков тысяч токенов), смотрите документацию модели. Если поставить больше потолка — провайдер начнёт отклонять запросы, и бот напишет об этом отдельным уведомлением; - разбить MR/PR на части поменьше — большие диффы и ревьюируются хуже;
- исключить сгенерированные файлы через
ignoreв config.yml — меньше находок, короче ответ.
Ревью не выполнено: провайдер перегружен или лимитирует
В логах — overloaded_error (у Anthropic это HTTP 529), ответ 503/529 с «overloaded» у OpenAI-совместимых либо затяжной 429 (rate limit — исчерпан лимит запросов или токенов вашего тарифа у вендора). Это состояние на стороне провайдера: ключ, сеть и конфигурация обычно ни при чём.
Бот лечит это сам: повторяет ревью с нарастающими паузами (несколько попыток, окно около 7 минут) — обычно перегрузка проходит за это время и ревью публикуется как обычно. Если не прошла, бот пишет уведомление в MR/PR и запустится заново на следующем пуше.
Когда это повторяется часто на потоке — посмотрите статус-страницу провайдера и лимиты тарифа; помогает выбрать менее загруженную модель того же вендора (первая роль llm.generators в config.yml) либо сменить вендора основного ревью — backend роли или LLM_PROVIDER/LLM_MODEL в окружении бота. Дополнительные роли (вторые generators, llm.judges, llm.arbiter) здесь не спасут: они добавляют вызовы поверх основного генератора, а не подменяют его, — как и именованные бэкенды LLM_BACKEND_<ИМЯ>_*, которые работают только для этих дополнительных ролей.
Ревью не выполнено: endpoint или модель не найдены
В MR/PR — уведомление «LLM-провайдер ответил, но по указанному адресу ничего нет», в логах — 404 либо model_not_found. Сеть тут исправна: запрос дошёл, ответа по этому адресу просто не существует. Две обычные причины — LLM_BASE_URL без /v1 у OpenAI-совместимого сервера (адрес должен указывать на API, а не на корень сайта) и опечатка в LLM_MODEL либо имя модели, которого у вендора нет.
Повторов здесь не будет намеренно: с той же настройкой ответ не изменится, и пять попыток только сожгли бы время. Поправьте адрес или имя модели — следующий пуш запустит ревью. Проверить настройку до коммита можно командой reviewgate doctor: она опрашивает endpoint живьём, не тратя токенов.
Ревью остановлено по бюджету (🛑 в сводке)
Задан потолок трат (LLM_BUDGET_TOKENS / LLM_BACKEND_<ИМЯ>_BUDGET_TOKENS), и расход какого-то бэкенда пробил тройной лимит — прогон остановлен на ближайшем чекпойнте, найденное к этому моменту опубликовано (находки могут быть не подтверждены судьёй — это сказано в сводке). Барьеры фиксированные и не настраиваются: 80% — нотис, 100% — громкий нотис (ревью выполняется целиком), 300% — стоп. Такое превышение — почти всегда не «дорогое ревью», а аномалия: гигантский сгенерированный дифф, зацикленный агент, карусель ретраев. Что делать: посмотрите блок 🔬 диагностики (строка «Бюджет прогона (токены)»), проверьте состав диффа и раскладку ролей; если расход ожидаем — поднимите лимит в env инсталляции. Для автоматизации срез машиночитаем: в JSON-отчёте CLI и ответе MCP — поле budget (барьер, расход и лимит по бэкендам), в метриках инсталляции — budget_barrier и tokens_by_backend для любой поверхности, включая прогоны CLI.
Ключ или токен скопирован из мессенджера
Симптом бывает разный — бот не стартует, reviewgate doctor ругается на сервер ревью, прогон падает, — а причина одна: в значении есть символ вне ASCII. Кириллические с, е, р и «умные» кавычки визуально неотличимы от латиницы, а в HTTP-заголовок они не проходят.
Мы называем такое прямо. Для ключей LLM в сообщении указаны позиция символа, сам символ и его код (эти сообщения печатаются по-английски), для токенов вендоров — позиция символа. Лечение — выпустить ключ или токен заново и скопировать целиком, без выделения мышью в переписке. Проверка стоит до первого платного вызова, поэтому дефект ловится стартом бота и reviewgate doctor, а не в середине ревью.
Родственный случай — токен, разорванный при копировании на две строки. Запрос с таким значением уходит нормально и получает от GitLab обычное «доступа нет», из-за чего люди идут к администратору за правами, хотя дело в лишнем пробеле. Такое мы тоже называем словами. Лишние пробелы и перевод строки по краям значения снимаются молча — это шум копирования, а не часть токена.
Логи
Смотреть логи бота — docker compose logs app; каждая строка ревью помечена тегом джобы. Централизованный сбор (Graylog / ELK / Loki), структурный LOG_FORMAT=json и рецепт GELF-драйвера вынесены на отдельную страницу «Логи и мониторинг».
Сводка: симптом → причина → что делать
| симптом | причина | решение |
|---|---|---|
| Бот не реагирует на MR/PR, в Recent events пусто | вебхук не настроен или не доставляется | пройдите проверки выше; diagnose.sh --project N |
| GitHub: доставки 202, но бот молчит | событие не подписано либо права App не приняты инсталляцией | раздел «GitHub: бот молчит…» выше |
| Recent events: ошибка соединения / timeout | GitLab не достучится во внутреннюю сеть | включите Outbound requests (раздел «Локальная сеть») |
| В MR/PR: «endpoint или модель не найдены» | LLM_BASE_URL без /v1 либо неверное имя модели | поправьте адрес/модель; проверка — reviewgate doctor |
| Recent events: 401 | секрет вебхука не совпал | сверьте Secret token ↔ .env, перезапустите бот |
| Recent events: 413 или 400 | тело события не прочитано — оно потеряно | обновите образ; повторите доставку (Resend в GitLab, Redeliver в GitHub) |
docker compose pull падает или висит | реестр недоступен | зеркало Docker Hub (раздел «Образ не скачивается») |
| Бот не стартует, в логах «Некорректная конфигурация» | нет обязательной переменной | заполните .env (раздел «Конфигурация») |
/api/health не отвечает | контейнер не поднялся или порт не проброшен | смотрите логи; задайте --bot-url |
| Ревью пустое или коммент про ошибку ключа | ключ LLM невалиден или нет кредитов | проверьте ключ LLM и баланс |
| В логах таймаут к провайдеру | нет рабочего прокси | задайте LLM_PROXY |
«Контекст не помещается», в логах prompt is too long | в изменения попал большой или сгенерированный код | ignore для генерируемого; см. раздел выше |
«Ответ не уместился в лимит выхода», в логах truncated by max_tokens | рассуждения модели + объём находок больше LLM_MAX_TOKENS | поднять LLM_MAX_TOKENS; см. раздел выше |
«Провайдер перегружен», в логах overloaded_error | временная нагрузка на стороне LLM-провайдера | обычно проходит само; см. раздел выше |
| В сводке «Community (ключ привязан к другой инсталляции)» | ключ выдан на другую инсталляцию — привязка не совпала ни с одним настроенным вендором | проверить лицензию |