Автоматические комментарии к GitHub PR через вебхуки
Это руководство показывает, как подключить Hermes Agent к GitHub так, чтобы он автоматически получал дифф pull request, анализировал изменения кода и публиковал комментарий — по событию вебхука, без ручного запуска.
Когда PR открывается или обновляется, GitHub отправляет POST-запрос на ваш инстанс Hermes. Hermes запускает агент с промптом, который инструктирует его получить дифф через gh CLI, а ответ публикуется в треде PR.
Если публичного URL нет или хочется быстро стартовать — смотрите Build a GitHub PR Review Agent. Там cron-задания опрашивают PR по расписанию, работает за NAT и фаерволами.
Полный справочник по платформе вебхуков (все опции конфига, типы доставки, динамические подписки, модель безопасности) — в Webhooks.
Полезная нагрузка вебхука содержит данные, контролируемые злоумышленником: заголовки PR, сообщения коммитов и описания могут включать вредоносные инструкции. Если эндпоинт открыт в интернет — запускайте шлюз в изолированной среде (Docker, SSH-бэкенд). Подробнее в разделе безопасности.
Что потребуется
- Hermes Agent установлен и работает (
hermes gateway) ghCLI установлен и авторизован на хосте шлюза (gh auth login)- Публично доступный URL вашего инстанса Hermes (см. Локальное тестирование через ngrok, если работаете локально)
- Доступ администратора к репозиторию GitHub (нужен для управления вебхуками)
Шаг 1 — Включить платформу вебхуков
Добавьте в ~/.hermes/config.yaml:
platforms:
webhook:
enabled: true
extra:
port: 8644 # по умолчанию; измените, если порт занят
rate_limit: 30 # макс. запросов в минуту на маршрут (не глобальный лимит)
routes:
github-pr-review:
secret: "your-webhook-secret-here" # должен точно совпадать с секретом вебхука GitHub
events:
- pull_request
# Агент получает дифф перед ревью.
# {number} и {repository.full_name} подставляются из полезной нагрузки GitHub.
prompt: |
A pull request event was received (action: {action}).
PR #{number}: {pull_request.title}
Author: {pull_request.user.login}
Branch: {pull_request.head.ref} → {pull_request.base.ref}
Description: {pull_request.body}
URL: {pull_request.html_url}
If the action is "closed" or "labeled", stop here and do not post a comment.
Otherwise:
1. Run: gh pr diff {number} --repo {repository.full_name}
2. Review the code changes for correctness, security issues, and clarity.
3. Write a concise, actionable review comment and post it.
deliver: github_comment
deliver_extra:
repo: "{repository.full_name}"
pr_number: "{number}"
Ключевые поля:
| Поле | Описание |
|---|---|
secret (на уровне маршрута) | HMAC-секрет для этого маршрута. Если не указан — используется глобальный extra.secret. |
events | Список значений заголовка X-GitHub-Event для приёма. Пустой список = принимать всё. |
prompt | Шаблон; {field} и {nested.field} подставляются из полезной нагрузки GitHub. |
deliver | github_comment публикует через gh pr comment. log просто пишет в лог шлюза. |
deliver_extra.repo | Подставляется из полезной нагрузки, например org/repo. |
deliver_extra.pr_number | Подставляется номер PR из полезной нагрузки. |
Вебхук GitHub включает метаданные PR (заголовок, описание, имена веток, URL), но не дифф. Промпт выше инструктирует агента выполнить gh pr diff для получения изменений. Инструмент terminal входит в стандартный набор hermes-webhook — дополнительная настройка не нужна.
Шаг 2 — Запустить шлюз
hermes gateway
Должно появиться:
[webhook] Listening on 0.0.0.0:8644 — routes: github-pr-review
Проверяем работу:
curl http://localhost:8644/health
# {"status": "ok", "platform": "webhook"}
Шаг 3 — Зарегистрировать вебхук на GitHub
- Откройте репозиторий → Settings → Webhooks → Add webhook
- Заполните:
- Payload URL:
https://your-public-url.example.com/webhooks/github-pr-review - Content type:
application/json - Secret: то же значение, что в
secretконфига маршрута - Which events? → Select individual events → отметьте Pull requests
- Payload URL:
- Нажмите Add webhook
GitHub сразу отправит событие ping для проверки соединения. Оно безопасно игнорируется — ping не входит в ваш список events — и возвращает {"status": "ignored", "event": "ping"}. В консоль не попадает: логируется только на уровне DEBUG.
Шаг 4 — Открыть тестовый PR
Создайте ветку, запушьте изменение, откройте PR. В течение 30–90 секунд (в зависимости от размера PR и модели) Hermes опубликует ревью-комментарий.
Наблюдать за работой агента в реальном времени:
tail -f "${HERMES_HOME:-$HOME/.hermes}/logs/gateway.log"
Локальное тестирование через ngrok
Если Hermes работает на ноутбуке — используйте ngrok для публичного доступа:
ngrok http 8644
Скопируйте URL вида https://...ngrok-free.app и укажите его как Payload URL в GitHub. На бесплатном тарифе URL меняется при каждом перезапуске ngrok — обновляйте вебхук в GitHub каждую сессию. У платных аккаунтов ngrok есть статический домен.
Статический маршрут можно протестировать напрямую через curl — без аккаунта GitHub и реального PR.
deliver: log при локальном тестировании
Замените deliver: github_comment на deliver: log в конфиге во время тестирования. Иначе агент попытается опубликовать комментарий в несуществующий репозиторий org/repo#99 из тестовой полезной нагрузки — и это закончится ошибкой. Верните deliver: github_comment, когда результат промпта вас устроит.
SECRET="your-webhook-secret-here"
BODY='{"action":"opened","number":99,"pull_request":{"title":"Test PR","body":"Adds a feature.","user":{"login":"testuser"},"head":{"ref":"feat/x"},"base":{"ref":"main"},"html_url":"https://github.com/org/repo/pull/99"},"repository":{"full_name":"org/repo"}}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print "sha256="$2}')
curl -s -X POST http://localhost:8644/webhooks/github-pr-review \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: pull_request" \
-H "X-Hub-Signature-256: $SIG" \
-d "$BODY"
# Ожидаем: {"status":"accepted","route":"github-pr-review","event":"pull_request","delivery_id":"..."}
Затем смотрим работу агента:
tail -f "${HERMES_HOME:-$HOME/.hermes}/logs/gateway.log"
hermes webhook test <name> работает только для динамических подписок, созданных через hermes webhook subscribe. Маршруты из config.yaml он не читает.
Фильтрация по конкретным действиям
GitHub отправляет события pull_request для множества действий: opened, synchronize, reopened, closed, labeled и других. Список events фильтрует только по значению заголовка X-GitHub-Event — фильтровать по подтипу действия на уровне маршрутизации нельзя.
Промпт из шага 1 уже обрабатывает это: агент получает инструкцию останавливаться для событий closed и labeled.
Инструкция «остановиться» предотвращает полноценное ревью, но агент всё равно выполняется до конца при каждом событии pull_request вне зависимости от действия. Вебхуки GitHub фильтруют только по типу события (pull_request, push, issues и т.д.) — не по подтипу (opened, closed, labeled). Фильтра по подтипу на уровне маршрутизации нет. Для высоконагруженных репозиториев либо примите эти расходы, либо фильтруйте на стороне GitHub Actions, вызывая ваш URL условно.
Синтаксис Jinja2 и условные конструкции не поддерживаются.
{field}и{nested.field}— единственные подстановки. Всё остальное передаётся агенту дословно.
Использование навыка для единообразного стиля ревью
Загрузите навык Hermes, чтобы задать агенту постоянную роль ревьюера. Добавьте skills в маршрут внутри platforms.webhook.extra.routes в config.yaml:
platforms:
webhook:
enabled: true
extra:
routes:
github-pr-review:
secret: "your-webhook-secret-here"
events: [pull_request]
prompt: |
A pull request event was received (action: {action}).
PR #{number}: {pull_request.title} by {pull_request.user.login}
URL: {pull_request.html_url}
If the action is "closed" or "labeled", stop here and do not post a comment.
Otherwise:
1. Run: gh pr diff {number} --repo {repository.full_name}
2. Review the diff using your review guidelines.
3. Write a concise, actionable review comment and post it.
skills:
- review
deliver: github_comment
deliver_extra:
repo: "{repository.full_name}"
pr_number: "{number}"
Примечание: загружается только первый найденный навык из списка. Hermes не стекует несколько навыков — последующие записи игнорируются.
Отправка ответов в Slack или Discord
Замените поля deliver и deliver_extra внутри маршрута на нужную платформу:
# Внутри platforms.webhook.extra.routes.<route-name>:
# Slack
deliver: slack
deliver_extra:
chat_id: "C0123456789" # ID канала Slack (опустите, чтобы использовать домашний канал)
# Discord
deliver: discord
deliver_extra:
chat_id: "987654321012345678" # ID канала Discord (опустите, чтобы использовать домашний канал)
Целевая платформа должна быть включена и подключена в шлюзе. Если chat_id не указан, ответ уходит в настроенный домашний канал этой платформы.
Допустимые значения deliver: log · github_comment · telegram · discord · slack · signal · sms
Поддержка GitLab
Тот же адаптер работает с GitLab. GitLab использует X-Gitlab-Token для аутентификации (сравнение строк, не HMAC) — Hermes обрабатывает оба варианта автоматически.
Для фильтрации событий GitLab устанавливает X-GitLab-Event со значениями вроде Merge Request Hook, Push Hook, Pipeline Hook. Используйте точное значение заголовка в events:
events:
- Merge Request Hook
Поля полезной нагрузки GitLab отличаются от GitHub — например, {object_attributes.title} для заголовка MR и {object_attributes.iid} для номера. Проще всего изучить структуру полезной нагрузки через кнопку Test в настройках вебхука GitLab вместе с журналом Recent Deliveries. Либо вовсе опустите prompt из конфига маршрута — тогда Hermes передаст всю полезную нагрузку как форматированный JSON напрямую агенту, и ответ агента (видимый в логе шлюза при deliver: log) опишет её структуру.
Заметки о безопасности
- Никогда не используйте
INSECURE_NO_AUTHв продакшене — это полностью отключает проверку подписи. Только для локальной разработки. - Ротируйте секрет вебхука периодически и обновляйте его и в GitHub (настройки вебхука), и в
config.yaml. - Rate limiting — 30 запросов в минуту на маршрут по умолчанию (настраивается через
extra.rate_limit). При превышении возвращается429. - Дубликаты доставки (повторные попытки вебхука) дедуплицируются через кэш идемпотентности на 1 час. Ключ кэша —
X-GitHub-Deliveryпри наличии, затемX-Request-ID, затем метка времени в миллисекундах. Если ни один из заголовков delivery ID не установлен, повторы не дедуплицируются. - Инъекция промпта: заголовки PR, описания и сообщения коммитов контролируются злоумышленником. Вредоносные PR могут пытаться манипулировать действиями агента. При доступе из публичного интернета запускайте шлюз в изолированной среде (Docker, виртуальная машина).
Устранение неполадок
| Симптом | Что проверить |
|---|---|
401 Invalid signature | Секрет в config.yaml не совпадает с секретом вебхука GitHub |
404 Unknown route | Имя маршрута в URL не совпадает с ключом в routes: |
429 Rate limit exceeded | Превышено 30 запросов в минуту на маршрут — часто возникает при повторной доставке тестовых событий из UI GitHub; подождите минуту или увеличьте extra.rate_limit |
| Комментарий не опубликован | gh не установлен, не в PATH или не авторизован (gh auth login) |
| Агент запустился, но комментария нет | Проверьте лог шлюза — если агент вернул пустое или просто «SKIP», доставка всё равно пытается выполниться |
| Порт уже занят | Измените extra.port в config.yaml |
| Агент запустился, но ревьюирует только описание PR | В промпте нет инструкции gh pr diff — дифф не входит в полезную нагрузку вебхука |
| Не видно событие ping | Игнорируемые события возвращают {"status":"ignored","event":"ping"} только на уровне DEBUG — проверьте журнал доставки GitHub (репозиторий → Settings → Webhooks → ваш вебхук → Recent Deliveries) |
Вкладка Recent Deliveries GitHub (репозиторий → Settings → Webhooks → ваш вебхук) показывает точные заголовки запроса, полезную нагрузку, HTTP-статус и тело ответа для каждой доставки. Быстрее всего диагностировать проблемы именно здесь, не трогая логи сервера.
Полный справочник конфигурации
platforms:
webhook:
enabled: true
extra:
host: "0.0.0.0" # адрес привязки (по умолчанию: 0.0.0.0)
port: 8644 # порт (по умолчанию: 8644)
secret: "" # опциональный глобальный фолбэк-секрет
rate_limit: 30 # запросов в минуту на маршрут
max_body_bytes: 1048576 # ограничение размера полезной нагрузки в байтах (по умолчанию: 1 МБ)
routes:
<route-name>:
secret: "required-per-route"
events: [] # [] = принимать всё; иначе список значений X-GitHub-Event
prompt: "" # {field} / {nested.field} подставляются из полезной нагрузки
skills: [] # загружается только первый найденный навык
deliver: "log" # log | github_comment | telegram | discord | slack | signal | sms
deliver_extra: {} # repo + pr_number для github_comment; chat_id для остальных
Что дальше?
- Ревью PR по cron — опрос PR по расписанию, публичный эндпоинт не нужен
- Справочник по вебхукам — полный справочник по платформе
- Создать плагин — упакуйте логику ревью в переиспользуемый плагин
- Профили — запустите отдельный профиль ревьюера с собственной памятью и конфигурацией