使用 Webhook 自动化 GitHub PR 评论
本指南将引导你将 Hermes Agent 与 GitHub 连接,让它自动获取 Pull Request 的差异,分析代码变更,并发布评论——由 Webhook 事件触发,无需手动提示。
当 PR 被打开或更新时,GitHub 向你的 Hermes 实例发送 Webhook POST 请求。Hermes 运行 Agent,提示词指示其通过 gh CLI 获取差异,然后响应内容被发布回 PR 线程。
如果你没有公共 URL,或者只是想快速上手,请查看构建 GitHub PR 审查 Agent——使用定时任务按计划轮询 PR,可在 NAT 和防火墙后正常工作。
完整的 Webhook 平台参考(所有配置选项、投递类型、动态订阅、安全模型)请参阅 Webhooks。
Webhook 载荷包含攻击者可控的数据——PR 标题、提交消息和描述可能包含恶意指令。当你的 Webhook 端点暴露在互联网上时,在沙箱化环境(Docker、SSH 后端)中运行网关。参见下方的安全说明。
前提条件
- 已安装并运行 Hermes Agent(
hermes gateway) - 网关主机上已安装并认证了
ghCLI(gh auth login) - 你的 Hermes 实例可公开访问的 URL(如果在本地运行,请参见使用 ngrok 进行本地测试)
- 该 GitHub 仓库的管理员访问权限(管理 Webhook 所需)
步骤 1 — 启用 Webhook 平台
在 ~/.hermes/config.yaml 中添加以下内容:
platforms:
webhook:
enabled: true
extra:
port: 8644 # 默认值;如果此端口被占用请更改
rate_limit: 30 # 每条路由每分钟最大请求数(非全局上限)
routes:
github-pr-review:
secret: "your-webhook-secret-here" # 必须与 GitHub Webhook 密钥完全匹配
events:
- pull_request
# Agent 被指示在审查之前获取实际的差异。
# {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 Webhook 载荷包含 PR 元数据(标题、描述、分支名、URL),但不包含差异。上述提示词指示 Agent 运行 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 上注册 Webhook
- 前往你的仓库 → Settings → Webhooks → Add webhook
- 填写:
- Payload URL:
https://your-public-url.example.com/webhooks/github-pr-review - Content type:
application/json - Secret: 与路由配置中
secret设置的相同值 - Which events? → 选择单个事件 → 勾选 Pull requests
- Payload URL:
- 点击 Add webhook
GitHub 会立即发送一个 ping 事件来确认连接。它被安全地忽略——ping 不在你的 events 列表中——并返回 {"status": "ignored", "event": "ping"}。它只在 DEBUG 级别记录,所以在默认日志级别下不会出现在控制台。
步骤 4 — 打开测试 PR
创建一个分支,推送变更,并打开一个 PR。在 30–90 秒内(取决于 PR 大小和模型),Hermes 应该会发布一条审查评论。
要实时跟踪 Agent 的进度:
tail -f "${HERMES_HOME:-$HOME/.hermes}/logs/gateway.log"
使用 ngrok 进行本地测试
如果 Hermes 在你的笔记本上运行,使用 ngrok 来暴露它:
ngrok http 8644
复制 https://...ngrok-free.app URL,并将其用作你的 GitHub Payload URL。在免费 ngrok 套餐上,每次 ngrok 重启 URL 都会变化——每次会话都要更新你的 GitHub Webhook。付费 ngrok 账号可以获得静态域名。
你可以用 curl 直接对静态路由进行冒烟测试——不需要 GitHub 账号或真实的 PR。
deliver: log在测试时将 deliver: github_comment 改为 deliver: log。否则 Agent 会尝试向测试载荷中的假 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":"..."}
然后观察 Agent 运行:
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 中的提示词已通过指示 Agent 对 closed 和 labeled 事件提前停止来处理这个问题。
"stop here"指令阻止了有实质内容的审查,但 Agent 仍然对每个 pull_request 事件运行直到完成,无论操作是什么。GitHub Webhook 只能按事件类型过滤(pull_request、push、issues 等),无法按操作子类型(opened、closed、labeled)过滤。路由层面没有针对子操作的过滤器。对于高流量仓库,接受这个成本,或者用有条件调用你的 Webhook URL 的 GitHub Actions 工作流在上游进行过滤。
不存在 Jinja2 或条件模板语法。
{field}和{nested.field}是唯一支持的替换。其他内容都原样传给 Agent。
使用技能保持一致的审查风格
加载一个 Hermes 技能,给 Agent 一致的审查人格。在 config.yaml 中的 platforms.webhook.extra.routes 路由里添加 skills:
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" # Slack 频道 ID(省略则使用配置的主频道)
# Discord
deliver: discord
deliver_extra:
chat_id: "987654321012345678" # Discord 频道 ID(省略则使用主频道)
目标平台也必须在网关中启用并已连接。如果省略 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 不同——例如,MR 标题用 {object_attributes.title},MR 编号用 {object_attributes.iid}。发现完整载荷结构最简单的方法是使用 GitLab Webhook 设置中的 Test 按钮,配合 Recent Deliveries 日志。或者,在路由配置中省略 prompt——Hermes 会将完整载荷以格式化 JSON 直接传给 Agent,Agent 的响应(在网关日志中用 deliver: log 可见)将描述其结构。
安全说明
- 永远不要在生产环境中使用
INSECURE_NO_AUTH— 它完全禁用签名验证。仅用于本地开发。 - 定期轮换 Webhook 密钥,并在 GitHub(Webhook 设置)和
config.yaml中同步更新。 - 速率限制默认为每条路由每分钟 30 次请求(可通过
extra.rate_limit配置)。超过后返回429。 - 重复投递(Webhook 重试)通过 1 小时幂等性缓存去重。缓存键依次为
X-GitHub-Delivery(若存在)、X-Request-ID,然后是毫秒级时间戳。当两个投递 ID 头都未设置时,重试不去重。 - 提示词注入: PR 标题、描述和提交消息是攻击者可控的。恶意 PR 可能尝试操纵 Agent 的行为。在暴露于公共互联网时,在沙箱化环境(Docker、VM)中运行网关。
故障排查
| 症状 | 检查项 |
|---|---|
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) |
| Agent 运行但没有评论 | 检查网关日志——如果 Agent 输出为空或只是"SKIP",投递仍然会被尝试 |
| 端口已被占用 | 在 config.yaml 中修改 extra.port |
| Agent 运行但只审查了 PR 描述 | 提示词中没有包含 gh pr diff 指令——diff 不在 Webhook 载荷中 |
| 看不到 ping 事件 | 被忽略的事件在仅 DEBUG 日志级别返回 {"status":"ignored","event":"ping"}——检查 GitHub 的投递日志(仓库 → Settings → Webhooks → 你的 Webhook → Recent Deliveries) |
GitHub 的 Recent Deliveries 标签(仓库 → Settings → Webhooks → 你的 Webhook)显示每次投递的确切请求头、载荷、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 MB)
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: {} # github_comment 需要 repo + pr_number;其他平台需要 chat_id
下一步
- 基于 Cron 的 PR 审查 — 按计划轮询 PR,无需公共端点
- Webhook 参考 — Webhook 平台的完整配置参考
- 构建插件 — 将审查逻辑打包成可共享的插件
- 配置文件 — 运行一个专用的审查者配置文件,拥有自己的记忆和配置