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

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

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

💡 Tip

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

ℹ️ Info

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

⚠️ Warning

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


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

  • Установленный и активированный Агент Гермес («гермес-шлюз»)
  • Установленный и аутентифицированный 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

          # Агенту предписывается получить актуальный 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}"

Ключевые поля:

Поле Описание
секретно (на уровне маршрута) HMAC-секрет для этого маршрута. Если опущен, используется глобальный extra.secret.
события Список оценок заголовков X-GitHub-Event, которые принимаются. Пустой список = принимать все.
подсказка Шаблон; {field} и {nested.field} в качестве полезной нагрузки GitHub.
доставить github_comment публикуется через gh pr comment. log просто записывает в журнал шлюза.
deliver_extra.repo Подставляется, например, org/repo из-за большой нагрузки.
deliver_extra.pr_number Подставляется номер PR из полезной нагрузки.
Полезная нагрузка вебхука GitHub включает метаданные PR (заголовки, описания, имена веток, URL), но не отличается. Подсказка выше предлагает агенту увеличить показатель gh pr diff, чтобы получить актуальные изменения. Инструмент «терминал» входит в стандартный набор инструментов «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. Перейдите в свой репозиторий → ** НастройкиВеб-перехватчикиДобавить веб-перехватчик**.
  2. Заполните:
  3. URL-адрес полезной нагрузки: https://your-public-url.example.com/webhooks/github-pr-review
  4. Тип контента: application/json
  5. Секрет: то же значение, которое вы указали для секрета в конфигурации маршрута.
  6. Какие события? → Выберите события → от рекомендаций Pull-запросы
  7. Нажмите Добавить вебхук.

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 и подтвердите его как URL-адрес полезной нагрузки на GitHub. По бесплатному тарифу URL-адрес ngrok меняется при каждом перезапуске 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`.</div>
---

## Фильтрация по действиям

GitHub отправляет события `pull_request` для многих действий: `opened`, `synchronize`, `reopened`, `closed`, `labeled` и т.д. Список `events` фильтруется только постепенно заголовку `X-GitHub-Event`  он не может фильтровать по подтипу действия на уровне маршрутизации.

Подсказка в Шаге 1 уже обрабатывает это, предписывая агенту при наступлении события «закрыто» и «помечено».<div class="admonition admonition-warning"><p class="admonition-title">⚠️ Warning</p> Агент всё запускается и расходует токены
Инструкция «остановиться здесь» содержит содержательное ревью, но агент всё равнозначен до конца для каждого события `pull_request` независимо от действия. Вебхуки GitHub может фильтровать только по типам событий («pull_request», «push», «issues» и т.д.)  не по подтипу действия («opened», «closed», «labeled»). Фильтра на уровне маршрутизации по условиям нет. Для репозиториев с высокой активностью примите эту стоимость или отфильтруйте вышестоящим образом с помощью рабочего процесса GitHub Actions, который условно показывает ваш URL-адрес вебхука.</div>
> Синтаксис Jinja2 или условных шаблонов отсутствует. `{field}` и `{nested.field}`  единственные выгодные подстановки. Всё остальное передаётся агенту как есть.

---

## Использование навыка для единообразного стиля ревью

Загрузите [навык Hermes](/docs/user-guide/features/skills), чтобы дать агенту единообразную персону ревьюера. Добавьте `skills` в ваш маршрут внутри `platforms.webhook.extra.routes` в `config.yaml`:
```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.<имя-маршрута>:

# 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:
  - Merge Request Hook

Поля полезной нагрузки GitLab отличается от GitHub — например, {object_attributes.title} для заголовка MR и {object_attributes.iid} для номеров MR. Самый простой способ узнать всю структуру полезной нагрузки — кнопка Test на вебхуке GitLab в журнале Recent Deliveries. Альтернативно, пропустите prompt в конфигурации маршрута — тогда Hermes передаст полную полезную нагрузку в виде отформатированного JSON напрямую агенту, и ответ агента (видимый в логе шлюза с deliver: log) запишет ее структуру.


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

  • Никогда не используйте ограничение INSECURE_NO_AUTH в продакшене — это полностью отключает проверку соединения. Только для локальной разработки.
  • Периодически меняйте секрет вебхука и обновляйте его как в GitHub (настройки вебхука), так и в config.yaml.
  • Ограничение скорости — 30 запросов в минуту по маршруту по умолчанию (на основании extra.rate_limit). Превышение возвращает 429.
  • Дублирующиеся доставки (повторные попытки вебхука) дедуплицируются с помощью кэша идемпотентности на 1 час. Ключ кэша — X-GitHub-Delivery, если присутствует, затем X-Request-ID, затем метка времени в миллисекундах. Если ни один из заголовков ID доставки не установлен, повторные попытки не дедуплицируются.
  • Инъекция в подсказке: заголовки PR, описания и сообщения коммитов контролируются атакующими. Вредоносные пиарщики могут обвинять манипулировать действиями агента. Запускайте шлюз в изолированной среде (Docker, VM), когда он доступен из публичного Интернета.

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

Симптом Проверка
401 Неверная подпись Секрет в config.yaml не соответствует секрету вебхука GitHub
404 Неизвестный маршрут Имя маршрута в URL не соответствует ключу в routes:
429 Превышен лимит скорости Превышено 30 запросов/мин на маршруте — часто бывает при повторной доставке тестовых событий из интерфейса GitHub; подождите минуту или увеличьте extra.rate_limit
Комментарий не опубликован gh не установлен, не указан в PATH или не аутентифицирован (gh auth login)
Агент запускается, но комментариев нет Проверить шлюз шлюза — если вывод агента был пустым или использовался только «SKIP», доставка все равно принадлежит правообладателю
Порт уже занят Замените extra.port в config.yaml
Агент запускается, но эффект только описание PR В предложении нет инструкции gh pr diff — diff отсутствует в методах вебхука
Невидимые события пинг Игнорировать возможные события возвращают {"status":"ignored","event":"ping"} только на уровне DEBUG — проверьте доставку GitHub (репозиторий → Настройки → Webhooks → ваш вебхук → Последние поставки)

Вкладка последних поставок на GitHub (репозиторий → Настройки → Вебхуки → ваш вебхук) показывает точные заголовки запроса, полезную нагрузку, 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:
        <имя-маршрута>:
          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 для остальных

Что дальше?