使用 Webhook 自动化 GitHub PR 评论
本指南将引导您将 Hermes Agent 与 GitHub 连接,实现当拉取请求(PR)被打开或更新时,自动获取其代码差异(diff),分析变更内容,并通过 webhook 事件触发,在 PR 线程中自动发布评论——整个过程无需手动干预。
当 PR 被创建或更新时,GitHub 会向您的 Hermes 实例发送一个 webhook POST 请求。Hermes 会运行代理(agent),执行一个提示(prompt),指示其使用 gh CLI 工具获取 diff 内容,随后将分析结果回复至 PR 的评论线程。
如果您没有公网 URL,或希望快速上手,可参考 构建 GitHub PR 审查代理 —— 该方案使用定时任务(cron job)按计划轮询 PR,可在 NAT 或防火墙后正常工作。
有关完整的 webhook 平台参考(所有配置选项、交付类型、动态订阅、安全模型等),请参阅 Webhooks。
webhook 数据包包含攻击者可控的数据——PR 标题、提交信息和描述可能包含恶意指令。当您的 webhook 端点暴露在互联网上时,请务必在沙盒环境(如 Docker、SSH 后端)中运行网关。详情请见下方的“安全提示”章节。
先决条件
- 已安装并运行的 Hermes Agent(
hermes gateway) - 在网关主机上安装并认证的
ghCLI(gh auth login) - 可公开访问的 Hermes 实例 URL(若本地运行,可参考 使用 ngrok 进行本地测试)
- 对目标 GitHub 仓库的管理员权限(用于管理 webhook)
第一步 —— 启用 webhook 平台
将以下内容添加到您的 ~/.hermes/config.yaml 配置中:
platforms:
webhook:
enabled: true
extra:
port: 8644 # default; change if another service occupies this port
rate_limit: 30 # max requests per minute per route (not a global cap)
routes:
github-pr-review:
secret: "your-webhook-secret-here" # must match the GitHub webhook secret exactly
events:
- pull_request
# The agent is instructed to fetch the actual diff before reviewing.
# {number} and {repository.full_name} are resolved from the GitHub payload.
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 payload 中解析。 |
deliver | github_comment 通过 gh pr comment 发送回复。log 仅写入网关日志。 |
deliver_extra.repo | 解析为 payload 中的 e.g. org/repo。 |
deliver_extra.pr_number | 解析为 payload 中的 PR 编号。 |
GitHub webhook payload 包含 PR 的元数据(标题、描述、分支名、URL),但 不包含 diff。上述提示已指示代理运行 gh pr diff 来获取实际的代码变更。terminal 工具已包含在默认的 hermes-webhook 工具集中,无需额外配置。
第二步 —— 启动网关
hermes gateway
您应看到如下输出:
[webhook] Listening on 0.0.0.0:8644 — routes: github-pr-review
验证是否正在运行:
curl http://localhost:8644/health
# {"status": "ok", "platform": "webhook"}
第三步 —— 在 GitHub 上注册 webhook
- 进入您的仓库 → 设置 → Webhooks → 添加 webhook
- 填写:
- Payload URL: `https://your-public-url.example.com/webhooks/github-pr-review``
- 内容类型:
application/json - 密钥(Secret): 与路由配置中设置的
secret值保持一致 - 哪些事件? → 选择单个事件 → 勾选 Pull requests
- 点击 添加 webhook
GitHub 会立即发送一个 ping 事件以确认连接。该事件会被安全忽略——因为 ping 不在您的 events 列表中——并返回 {"status": "ignored", "event": "ping"}。它仅在 DEBUG 日志级别记录,因此在默认日志级别下不会显示在控制台中。
第四步 —— 创建测试 PR
创建一个新分支,推送更改,然后打开一个 PR。30 至 90 秒内(取决于 PR 大小和所用模型),Hermes 应会自动发布一条评审评论。
要实时跟踪代理的执行进度:
tail -f "${HERMES_HOME:-$HOME/.hermes}/logs/gateway.log"
使用 ngrok 进行本地测试
如果 Hermes 运行在您的笔记本电脑上,请使用 ngrok 暴露服务:
ngrok http 8644
复制生成的 `https://...ngrok-free.app`` URL,并作为 GitHub 的 Payload URL 使用。免费版 ngrok 每次重启都会更换 URL —— 每次会话需更新 GitHub webhook。付费版 ngrok 可获得固定域名。
您可以直接使用 curl 对静态路由进行烟雾测试——无需 GitHub 账户或真实 PR。
deliver: log测试期间,请将配置中的 deliver: github_comment 改为 deliver: log。否则代理会尝试向测试 payload 中的假 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"
# Expected: {"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 webhook 仅支持按事件类型(如 pull_request、push、issues)过滤,不支持按操作子类型(如 opened、closed、labeled)过滤。目前无路由级子动作过滤机制。对于高频率仓库,建议接受此成本,或在上游使用 GitHub Actions 工作流有条件地调用您的 webhook 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 字段替换为您目标平台的配置:
# Inside platforms.webhook.extra.routes.<route-name>:
# Slack
deliver: slack
deliver_extra:
chat_id: "C0123456789" # Slack channel ID (omit to use the configured home channel)
# Discord
deliver: discord
deliver_extra:
chat_id: "987654321012345678" # Discord channel ID (omit to use home channel)
目标平台也必须在网关中启用并完成连接。若 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 编号。最简单的方法是通过 Webhook 设置中的 **测试** 按钮,结合 **最近交付记录** 日志来探索完整的负载结构。或者,在路由配置中省略 ``prompt`` — 这样 Hermes 会将完整原始负载以格式化 JSON 的形式直接传递给代理,而代理的响应(可在网关日志中通过 ``deliver: log`` 查看)将揭示其结构。
---
<a id="security-notes"></a>
## 安全提示
- **切勿在生产环境中使用 ``INSECURE_NO_AUTH``** — 它会完全禁用签名验证,仅用于本地开发。
- **定期更换 Webhook 密钥**,并确保在 GitHub(Webhook 设置)和你的 ``config.yaml`` 中同步更新。
- **默认速率限制为每分钟 30 次请求/路由**(可通过 ``extra.rate_limit`` 配置)。超出限制将返回 ``429``。
- **重复交付**(Webhook 重试)通过 1 小时的幂等性缓存进行去重。缓存键优先级为:``X-GitHub-Delivery``(若存在),其次为 ``X-Request-ID``,最后为毫秒级时间戳。若未设置交付 ID 头部,则重试 **不会** 被去重。
- **提示注入风险**:PR 标题、描述和提交信息由攻击者控制。恶意 PR 可能试图操纵代理行为。当网关暴露于公网时,务必在沙箱环境(如 Docker 或虚拟机)中运行。
---
## 故障排查
| 现象 | 排查要点 |
|---|---|
| ``401 Invalid signature`` | 配置文件 `config.yaml` 中的密钥与 GitHub Webhook 密钥不匹配 |
| ``404 Unknown route`` | URL 中的路由名称与 ``routes:`` 中的键不一致 |
| ``429 Rate limit exceeded`` | 单个路由每分钟超过 30 次请求 — 常见于从 GitHub UI 重新发送测试事件;请等待一分钟,或提高 ``extra.rate_limit`` 限制 |
| 未发布评论 | ``gh`` 未安装、不在 PATH 中,或未认证(``gh auth login``) |
| 代理运行但无评论 | 检查网关日志 — 若代理输出为空或仅为 "SKIP",仍会尝试交付 |
| 端口已被占用 | 修改 `config.yaml` 中的 ``extra.port`` |
| 代理运行但仅审查 PR 描述 | 提示中未包含 ``gh pr diff`` 指令 — 差异内容未包含在 Webhook 负载中 |
| 无法看到 ping 事件 | 被忽略的事件仅在 DEBUG 日志级别显示 ``{"status":"ignored","event":"ping"}`` — 请检查 GitHub 的交付日志(仓库 → 设置 → Webhooks → 你的 Webhook → 最近交付记录) |
**GitHub 的“最近交付”标签页**(仓库 → 设置 → Webhooks → 你的 Webhook)可显示每次交付的完整请求头、负载、HTTP 状态码及响应体,是诊断问题最快的方式,无需查看服务器日志。
---
## 完整配置参考
````yaml
platforms:
webhook:
enabled: true
extra:
host: "0.0.0.0" # bind address (default: 0.0.0.0)
port: 8644 # listen port (default: 8644)
secret: "" # optional global fallback secret
rate_limit: 30 # requests per minute per route
max_body_bytes: 1048576 # payload size limit in bytes (default: 1 MB)
routes:
<route-name>:
secret: "required-per-route"
events: [] # [] = accept all; otherwise list X-GitHub-Event values
prompt: "" # {field} / {nested.field} resolved from payload
skills: [] # first matching skill is loaded (only one)
deliver: "log" # log | github_comment | telegram | discord | slack | signal | sms
deliver_extra: {} # repo + pr_number for github_comment; chat_id for others
下一步
- 基于定时任务的 PR 审查 — 按计划轮询 PR,无需公开端点
- Webhook 参考文档 — Webhook 平台的完整配置说明
- 构建插件 — 将评审逻辑打包为可共享的插件
- 配置文件(Profiles) — 运行专用评审配置文件,拥有独立记忆与配置