команда · треды

Не согласны с замечанием: как поспорить, чтобы бот ответил

Бот оставил замечание на строке кода в вашем MR/PR, а у вас есть причина, по которой его тут быть не должно: лог внутренний, код старый, приём сделан намеренно. Можно закрыть тред и идти дальше — так делают часто, и именно так ревью превращается в формальность. А можно возразить в треде и получить ответ.

Вот пример — тред в публичном MR нашего учебного репозитория на GitLab. Разработчик возражает: логи не покидают кластер, хранятся 14 дней, доступ к ним только у дежурных, а поддержка ищет счёт по почте, потому что номера клиент не знает. Бот отвечает, что понимает довод про закрытый контур, но правило команды рассчитано как раз на такой случай: чтобы найти запись, достаточно идентификатора. Искать счёт по почте поддержка должна в базе или через отдельный механизм, а не в логах, — и замечание бот оставляет в силе.

Ответ бота — это один вызов модели, несколько центов. Согласны вы с ним или нет — отдельный вопрос; суть в том, что это довод, на который можно ответить, а не вердикт, который остаётся только закрыть.

Ответы по умолчанию выключены. Их включает reply.enabled: true в .reviewgate/config.yml той ветки, из которой сделан MR/PR, или REPLY_ENABLED=true у самого бота — тогда на всю инсталляцию (явное reply.enabled: false в репозитории это гасит). И вебхук должен доставлять события комментариев. Обе ловушки разобраны ниже.

Где бот отвечает сам, а где его нужно позвать

В своих тредах бот отвечает на любую реплику, звать его не нужно. Свои — это треды его замечаний на строках кода, а в GitLab ещё и тред под сводкой.

В ленте PR на GitHub (вкладка Conversation) всё иначе: сводка там — обычный комментарий в общей ленте, и каждая следующая реплика для бота — отдельный разговор. Здесь, как и в чужом треде, бота нужно позвать по имени, а ответ придёт новым комментарием в ленту.

В чужом треде — разговоре двух людей или на строке, которой бот не касался, — бота нужно позвать по имени, то есть упомянуть учётку, от которой он пишет: в GitLab это пользователь, чьим токеном работает бот, в GitHub — имя приложения (у нас @reviewgate-bot). Упоминание срабатывает в любом месте текста, даже внутри цитаты или блока кода. Без упоминания у бота нет оснований считать, что разговор его касается, а инструмент ревью, который влезает в каждое обсуждение без приглашения, за день становится шумом.

Свой тред бот узнаёт технически: первый комментарий в нём написан учёткой бота и несёт его скрытый маркер. Человек, процитировавший замечание, «своего» треда не открывает — иначе беседу начинал бы каждый процитированный комментарий ревью. По той же учётке бот узнаёт и свои реплики и на них не отвечает. Поэтому, если бот работает с личным токеном человека (в GitLab — токен пользователя, в GitHub — PAT), этот человек поговорить с ботом не сможет: его реплики бот считает своими.

.reviewgate/config.yml
# .reviewgate/config.yml — in the BRANCH the pull request comes from
reply:
  enabled: true              # off by default
  max_replies_per_thread: 3  # the only brake on a long argument

Три ответа на тред по умолчанию (reply.max_replies_per_thread). Лимит проверяется до вызова модели: дойдя до него, бот замолкает. В ленте PR на GitHub лимит общий на весь PR. Других тормозов между двумя ботами нет: чужих ботов код не распознаёт, и если в том же треде отвечает ассистент коллеги, разговор остановит только лимит.

Пока лимит не исчерпан, каждая реплика в треде бота — платный вызов модели, даже «спасибо». Промолчать модель решает сама, уже получив всё на вход, так что молчание тоже оплачено: в треде от него следа нет, в лимит оно не идёт, а в логе бота остаётся строка ↩ The model stayed silent. На наших прогонах с sonnet-5 по нынешней цене первый ответ в треде стоил 3–4 цента, следующие — от половины цента до полутора: вход шёл из кэша Anthropic.

В закрытых тредах бот не отвечает никогда — откройте тред заново, если ответ нужен. А вот черновики бота не останавливают: пропуск ревью черновика к разговорам не относится, и вопрос в черновике получит ответ — как и в закрытом или уже смерженном MR.

Правка комментария не считается. Бот реагирует на новые комментарии, а не на правки. Переписанное на месте возражение для него невидимо; чтобы спросить снова, напишите новый комментарий.

Кто отвечает: первая модель в generators вашей схемы, без судьи. Поэтому ответ хорош настолько, насколько хорош генератор, а не весь ансамбль. Модель для ответов можно сменить ключом reply.model — вендор при этом остаётся прежним; для другого вендора есть reply.backend.

Почему бот молчит

1. Ответы выключены, пока вы их не включите. reply.enabled по умолчанию выключен, и REPLY_ENABLED у бота тоже. Если ключ есть в репозитории, решает он: reply.enabled: true включает ответы даже при REPLY_ENABLED=false у бота, а reply.enabled: false выключает их даже при REPLY_ENABLED=true. Ключа в репозитории нет — действует REPLY_ENABLED, и в нём считается только буквальное true: 1 или yes ответы не включат. У команды, которая ответы не включала, бот делает ревью и не отвечает никогда — это не поломка, а дефолт продукта.

2. Конфиг читается из ветки MR, а не из целевой. Политика для MR берётся из той ветки, откуда он сделан. Если включить ответы в main, бот в открытых MR будет молчать, пока их ветки не подхватят изменение, — например, пока в них не вольют main. Учтите две вещи: такое вливание — новый платный прогон ревью, а старые комментарии бот заново не разбирает, так что возражение придётся написать ещё раз. Ветки не нужны, если включить ответы у бота через REPLY_ENABLED=true: это действует сразу во всех MR, где в ветке нет ключа reply.enabled.

3. Вебхук не доставляет комментарии. То, что ревью приходит, про ответы ничего не доказывает: они едут на других событиях. В GitLab хуку нужны Comments (note_events) вдобавок к событиям MR. В GitHub ответ в треде замечания приходит событием «Pull request review comment», а реплика в ленте PR — событием «Issue comment», которому нужно право приложения «Issues: read». Если этих событий в настройках нет, об этом никто не сообщит: ни GitLab с GitHub, ни бот ошибки не покажут — комментарий просто не дойдёт до бота, и тот промолчит.

4. Модель решила промолчать. На реплику вроде «поправил» или «спасибо» отвечать нечего, и модель может промолчать сама. В треде ничего не появится, а в логе бота будет строка ↩ The model stayed silent.

как понять, в чём делоНапишите комментарий в своём же треде бота и найдите его в логах бота (docker compose logs --since 10m app | grep -i reply). Строки Queued a reply нет — событие не пришло: дело в вебхуке. Она есть, а за ней ⏭ Reply skipped: — бот получил комментарий и отказался, причина написана в той же строке: ответы выключены, тред закрыт, лимит исчерпан, тред чужой без упоминания. ↩ The model stayed silent — бот спросил модель, и она решила промолчать.

Спор целиком

Замечание на строке, где почта попадает в лог, — тред целиком на GitLab:

в треде
🟠 no-customer-data-in-logs 📐 — В лог пишется e-mail клиента (command.customerEmail).
   Правило команды запрещает класть данные клиента в логи — для поиска записи достаточно
   идентификатора счёта.

Возражение — настоящий довод, а не «ты неправ»:

в треде
Не соглашусь. Наши логи не покидают кластер: они идут в самостоятельно поднятый сборщик
внутри той же сети, хранятся 14 дней, доступ только у дежурной команды. Почта — это то, по
чему поддержка ищет, когда клиент звонит про пропавший счёт: номера счёта клиент не знает.

С учётом этого, замечание всё ещё стоит того, чтобы его чинить?

Ответ бота, через двадцать секунд:

в треде
Понимаю аргумент про закрытый контур и удобство поиска, но правило команды здесь как
раз рассчитано на этот случай: «идентификатора достаточно, чтобы найти запись в базе».
Если поддержке для звонка по email нужно найти счёт, это должно решаться поиском по
email в БД (или отдельным индексом/API поддержки), а не хранением email в логах — логи
не место для PII даже при ограниченном доступе и retention в 14 дней (audit/comfort с
будущими требованиями compliance, ротацией доступа дежурных и т.д.). Оставляю
замечание в силе: предлагаю логировать invoice.id (и, при желании, invoice.customerId,
если он есть), а поиск по email вынести в отдельный, контролируемый механизм.

Бот признал довод, а не повторил замечание. Он показал, на что правило рассчитано: «идентификатора достаточно, чтобы найти запись в базе» — ровно тот случай, о котором возражение. И он ответил на исходную потребность, предложив другой способ решить задачу поддержки, а не просто отказав. При этом он не сдался: инструмент, который соглашается с последним возразившим, — не ревьюер, а зеркало.

Оба разговора публичны — PR на GitHub и MR на GitLab нашего учебного репозитория. Русский — отдельный прогон, а не перевод, и к той же позиции бот пришёл сам.

Дайте ему факт, которого у него быть не может

что бот видит, когда отвечаетОтвечая, бот видит тред, по 30 строк кода выше и ниже строки замечания, стандарты команды и заголовок с описанием MR — и больше ничего: ни диффа, ни файлов целиком (под сводкой и в ленте PR на GitHub кода он не видит вовсе). Он не знает, что ваши логи не выходят за пределы сети, что модуль через месяц удалят, что приём согласован в ADR два года назад. Именно такие возражения и стоит писать: только они добавляют информацию. «Это ложное срабатывание» не добавляет ничего, «неважно» — тоже. Ответ будет ровно настолько содержательным, насколько содержательно возражение.

Если факт касается не одной строки, а всего MR или проекта, положите его туда, где его увидит каждый прогон: в описание MR — его читают и ревью, и ответы — или в dont_flag в конфиге.

ответ не снимает замечаниеЭто стоит сказать прямо, потому что ждут обратного: разговор ничего не меняет ни в сводке, ни в гейте. Замечание остаётся, и проверка остаётся красной, если была красной. Ответ — это довод, а не вердикт. Что снимает замечание с гейта, разобрано на странице «Треды бота закрывают, а код не меняется»: исправление кода, закрытие треда — с оговорками, — правка правила или конфига.

В конце спора у вас развилка. Если бот был неправ, потому что ваше правило написано без исключения, — поправьте правило: одна фраза в его тексте снимает весь класс таких замечаний. Если бот был прав, но чинить сейчас вы не будете, — это решение, а не спор: напишите в треде почему и закройте его. В GitLab это одно действие — галочка «Resolve thread» под ответом, и бот отвечать не станет; в GitHub закрытие — отдельная кнопка, и бот может успеть ответить.

Лимит — это достоинство. Спор с инструментом ревью на десять сообщений — пустая трата и времени, и денег: если за три ответа бота вопрос не решился, разногласие не про строку, а про правило. Решайте его в конфиге.

Когда что-то пошло не так

Бот ответил общими словами. Обычно это значит, что в возражении не было нового факта. Напишите то, чего бот видеть не может: ограничение, договорённость команды, план на этот код.

Бот согласился, а замечание осталось. Так и должно быть: ответ ничего не меняет ни в сводке, ни в гейте. Раз замечание неверное, закройте тред, а если его породило ваше правило — поправьте правило, иначе в следующем MR оно может вернуться.

Тред затих после трёх ответов. Это лимит. Откройте новый тред и позовите бота по имени — у нового треда лимит свой (кроме ленты PR на GitHub: там он общий на весь PR). А лучше закрепите решение в конфиге, тогда оно подействует и на следующие MR.

Бот ответил в черновике, где вы этого не ждали. Пропуск черновиков касается только ревью, а не разговоров: на вопрос в черновике бот ответит, и стоит этот ответ столько же, сколько любой другой.

Дальше