跳到主要内容

使用 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
  • 网关主机上已安装并认证了 gh CLIgh 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 载荷中解析。
delivergithub_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

  1. 前往你的仓库 → SettingsWebhooksAdd webhook
  2. 填写:
    • Payload URL: https://your-public-url.example.com/webhooks/github-pr-review
    • Content type: application/json
    • Secret: 与路由配置中 secret 设置的相同值
    • Which events? → 选择单个事件 → 勾选 Pull requests
  3. 点击 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 事件:openedsynchronizereopenedclosedlabeled 等。events 列表只按 X-GitHub-Event 头值过滤——无法在路由层面按操作子类型过滤。

步骤 1 中的提示词已通过指示 Agent 对 closedlabeled 事件提前停止来处理这个问题。

Agent 仍然运行并消耗 Token

"stop here"指令阻止了有实质内容的审查,但 Agent 仍然对每个 pull_request 事件运行直到完成,无论操作是什么。GitHub Webhook 只能按事件类型过滤(pull_requestpushissues 等),无法按操作子类型(openedclosedlabeled)过滤。路由层面没有针对子操作的过滤器。对于高流量仓库,接受这个成本,或者用有条件调用你的 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

将路由中的 deliverdeliver_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 HookPush HookPipeline 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 signatureconfig.yaml 中的密钥与 GitHub Webhook 密钥不匹配
404 Unknown routeURL 中的路由名称与 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

下一步