Автоматические комментарии к PR на GitHub через вебхуки

Это руководство покажет, как подключить Hermes Agent к GitHub, чтобы он автоматически получал diff пул-реквеста, анализировал изменения в коде и публиковал комментарий — срабатывая от события вебхука без необходимости ручного запроса.

Когда PR открывается или обновляется, GitHub отправляет POST-запрос через вебхук на ваш экземпляр Hermes. Hermes запускает агента с промптом, который предписывает получить diff с помощью CLI gh, а ответ публикуется обратно в обсуждение PR.

tip Хотите более простую настройку без публичного эндпоинта? Если у вас нет публичного URL или вы хотите быстро начать, ознакомьтесь с Build a GitHub PR Review Agent — используется cron для опроса PR по расписанию, работает за NAT и файрволами.

info Справочная документация Полную справку по платформе вебхуков (все опции конфигурации, типы доставки, динамические подписки, модель безопасности) см. в разделе Webhooks.

warning Риск инъекции промпта Полезная нагрузка вебхука содержит данные, контролируемые атакующим — заголовки PR, сообщения коммитов и описания могут содержать вредоносные инструкции. Когда ваш вебхук-эндпоинт доступен из интернета, запускайте gateway в изолированном окружении (Docker, SSH backend). См. раздел безопасности ниже.


Предварительные требования


Шаг 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

          # Агенту предписывается получить актуальный diff перед ревью.
          # {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 просто записывает в лог gateway.
deliver_extra.repo Подставляется, например, как org/repo из полезной нагрузки.
deliver_extra.pr_number Подставляется как номер PR из полезной нагрузки.

note Полезная нагрузка не содержит код Полезная нагрузка вебхука GitHub включает метаданные PR (заголовок, описание, имена веток, URL), но не diff. Приведённый выше промпт предписывает агенту выполнить gh pr diff для получения актуальных изменений. Инструмент terminal включён в набор по умолчанию hermes-webhook, поэтому дополнительная настройка не требуется.


Шаг 2 — Запустите gateway

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. Заполните:

  3. Payload URL: https://your-public-url.example.com/webhooks/github-pr-review
  4. Content type: application/json
  5. Secret: то же значение, которое вы указали в secret в конфигурации маршрута
  6. Which events? → Выберите отдельные события → отметьте Pull requests

  7. Нажмите 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. На бесплатном тарифе ngrok URL меняется при каждом перезапуске ngrok — обновляйте вебхук GitHub в каждой сессии. Платные аккаунты ngrok предоставляют статический домен.

Вы можете протестировать статический маршрут напрямую через curl — без аккаунта GitHub или реального PR.

tip Используйте 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.

warning Агент всё равно запускается и расходует токены Инструкция «остановиться» предотвращает полноценное ревью, но агент всё равно выполняется до конца для каждого события pull_request независимо от действия. Вебхуки GitHub могут фильтровать только по типу события (pull_request, push, issues и т.д.) — не по подтипу действия (opened, closed, labeled). Фильтрации на уровне маршрутизации для поддействий нет. Для репозиториев с высокой нагрузкой примите это как неизбежные затраты или отфильтруйте upstream с помощью GitHub Actions workflow, который вызывает ваш 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 вместо GitHub

Замените поля 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 (опустите, чтобы использовать домашний канал)

Целевая платформа также должна быть включена и подключена в gateway. Если 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} для номера MR. Самый простой способ изучить полную структуру полезной нагрузки — кнопка Test в настройках вебхука GitLab в сочетании с журналом Recent Deliveries. Альтернативно, опустите prompt в конфигурации маршрута — Hermes передаст полную полезную нагрузку в виде форматированного JSON напрямую агенту, и ответ агента (видимый в логе gateway с deliver: log) опишет её структуру.


Заметки по безопасности


Устранение неполадок

Симптом Что проверить
401 Invalid signature Секрет в config.yaml не совпадает с секретом вебхука GitHub
404 Unknown route Имя маршрута в URL не соответствует ключу в routes:
429 Rate limit exceeded Превышен лимит 30 запросов/мин на маршрут — часто случается при повторной доставке тестовых событий из интерфейса GitHub; подождите минуту или увеличьте extra.rate_limit
Комментарий не опубликован gh не установлен, не в PATH или не аутентифицирован (gh auth login)
Агент запускается, но комментария нет Проверьте лог gateway — если вывод агента был пустым или содержал только «SKIP», доставка всё равно будет предпринята
Порт уже занят Измените extra.port в config.yaml
Агент запускается, но ревьюит только описание PR В промпте отсутствует инструкция gh pr diff — 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 для остальных

Что дальше?