Автоматические комментарии к 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)
  • gh CLI установлен и авторизован на хосте шлюза (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.
delivergithub_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

  1. Откройте репозиторий → SettingsWebhooksAdd webhook
  2. Заполните:
    • 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
  3. Нажмите 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 для остальных

Что дальше?

ESC