ByteNoteByteNote

字节笔记本

2026年5月16日

Hermes Agent GitHub PR Webhook:Webhook 驱动的自动代码审查

API中转
¥120

Hermes Agent 可以通过 Webhook 与 GitHub 集成,实现 Pull Request 的自动代码审查。当 PR 被创建或更新时,GitHub 会向 Hermes 实例发送 Webhook POST 请求,Hermes 随后启动 Agent,通过 gh CLI 获取 PR 的 diff,分析代码变更,并将审查评论自动发布回 PR 线程——整个过程无需人工干预。

:::tip 不想配置公网端点? 如果你没有公网 URL 或只想快速上手,可以参考 Build a GitHub PR Review Agent——该方案使用定时任务轮询 PR,无需公网暴露,适合 NAT 和防火墙后面的环境。 :::

:::info 参考文档 关于 Webhook 平台的完整参考(所有配置选项、投递类型、动态订阅、安全模型),请参阅 Webhooks。 :::

:::warning Prompt 注入风险 Webhook 载荷包含攻击者可控数据——PR 标题、提交信息和描述可能包含恶意指令。当 Webhook 端点暴露在公网时,务必在沙箱环境(Docker、SSH 后端)中运行 Gateway。详见下方安全注意事项。 :::


前置条件

  • Hermes Agent 已安装并运行(hermes gateway
  • gh CLI 已在 Gateway 主机上安装并认证(gh auth login
  • Hermes 实例拥有公网可达的 URL(本地运行可参考 使用 ngrok 进行本地测试
  • 拥有 GitHub 仓库的管理员权限(用于管理 Webhook)

Step 1 — 启用 Webhook 平台

~/.hermes/config.yaml 中添加以下配置:

yaml
platforms:
  webhook:
    enabled: true
    extra:
      port: 8644          # 默认端口;若被占用可修改
      rate_limit: 30      # 每个路由每分钟最大请求数(非全局限制)

      routes:
        github-pr-review:
          secret: "your-webhook-secret-here"   # 必须与 GitHub Webhook Secret 完全一致
          events:
            - pull_request

          # Agent 被指示在审查前先获取实际的 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 载荷中解析。
delivergithub_comment 通过 gh pr comment 发布评论;log 仅写入 Gateway 日志。
deliver_extra.repo解析为载荷中的 org/repo 格式。
deliver_extra.pr_number解析为载荷中的 PR 编号。

:::note 载荷中不包含代码 GitHub Webhook 载荷包含 PR 元数据(标题、描述、分支名、URL),但不包含 diff。上面的 prompt 指示 Agent 运行 gh pr diff 来获取实际变更。terminal 工具已包含在默认的 hermes-webhook 工具集中,无需额外配置。 :::


Step 2 — 启动 Gateway

bash
hermes gateway

你应该看到:

[webhook] Listening on 0.0.0.0:8644 — routes: github-pr-review

验证运行状态:

bash
curl http://localhost:8644/health
# {"status": "ok", "platform": "webhook"}

Step 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 日志级别记录,默认日志级别下不会在控制台显示。


Step 4 — 创建测试 PR

创建分支、推送变更并开启 PR。30–90 秒内(取决于 PR 大小和模型),Hermes 应当会发布一条审查评论。

实时查看 Agent 执行进度:

bash
tail -f "${HERMES_HOME:-$HOME/.hermes}/logs/gateway.log"

使用 ngrok 进行本地测试

如果 Hermes 运行在你的笔记本上,可以使用 ngrok 暴露服务:

bash
ngrok http 8644

复制 https://...ngrok-free.app URL 作为 GitHub 的 Payload URL。免费版 ngrok 每次重启后 URL 会变化——每次会话需要更新 GitHub Webhook。付费 ngrok 账户可获得静态域名。

你也可以直接用 curl 对静态路由进行冒烟测试——不需要 GitHub 账户或真实 PR。

:::tip 本地测试时使用 deliver: log 测试时将配置中的 deliver: github_comment 改为 deliver: log。否则 Agent 会尝试向测试载荷中的假 org/repo#99 仓库发布评论,导致失败。满意 prompt 输出后切回 deliver: github_comment。 :::

bash
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 运行:

bash
tail -f "${HERMES_HOME:-$HOME/.hermes}/logs/gateway.log"

:::note hermes webhook test <name> 仅适用于通过 hermes webhook subscribe 创建的动态订阅。它不会读取 config.yaml 中的路由。 :::


过滤特定 Action

GitHub 对 pull_request 事件发送多种 action:openedsynchronizereopenedclosedlabeled 等。events 列表仅按 X-GitHub-Event 请求头值进行过滤——无法在路由层面按 action 子类型过滤。

Step 1 中的 prompt 已通过指示 Agent 对 closedlabeled 事件提前终止来处理此问题。

:::warning Agent 仍会运行并消耗 Token "stop here" 指令阻止了有意义的审查,但 Agent 仍然会对每个 pull_request 事件完整运行,无论 action 是什么。GitHub Webhook 只能按事件类型(pull_requestpushissues 等)过滤,不能按 action 子类型(openedclosedlabeled)过滤。路由层面没有子 action 过滤器。对于高流量仓库,请接受此成本,或在上游使用 GitHub Actions 工作流有条件地调用你的 Webhook URL。 :::

不支持 Jinja2 或条件模板语法。{field}{nested.field} 是仅有的替换语法。其他内容会原样传递给 Agent。


使用 Skill 保持一致的审查风格

加载 Hermes Skill 可为 Agent 提供一致的审查风格。在 config.yamlplatforms.webhook.extra.routes 路由中添加 skills

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}"

注意: 只有列表中第一个被找到的 Skill 会被加载。Hermes 不会叠加多个 Skill——后续条目会被忽略。


将响应发送到 Slack 或 Discord

替换路由中的 deliverdeliver_extra 字段为目标平台:

yaml
# 在 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(省略则使用默认频道)

目标平台也必须在 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 HookPush HookPipeline Hook 等值。在 events 中使用精确的请求头值:

yaml
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 在 Gateway 日志中可见)会描述其结构。


安全注意事项

  • 生产环境切勿使用 INSECURE_NO_AUTH——它会完全禁用签名验证。仅用于本地开发。
  • 定期轮换 Webhook Secret,并在 GitHub(Webhook 设置)和 config.yaml 中同步更新。
  • 速率限制默认为每个路由 30 请求/分钟(可通过 extra.rate_limit 配置)。超限返回 429
  • 重复投递(Webhook 重试)通过 1 小时幂等缓存去重。缓存键依次为 X-GitHub-DeliveryX-Request-ID、毫秒时间戳。当两个投递 ID 请求头都未设置时,重试不会被去重。
  • Prompt 注入: PR 标题、描述和提交信息是攻击者可控的。恶意 PR 可能试图操控 Agent 的行为。暴露在公网时,务必在沙箱环境(Docker、VM)中运行 Gateway。

故障排查

症状检查项
401 Invalid signatureconfig.yaml 中的 Secret 与 GitHub Webhook Secret 不匹配
404 Unknown routeURL 中的路由名与 routes: 中的键不匹配
429 Rate limit exceeded超过每路由 30 请求/分钟限制——从 GitHub UI 重新投递测试事件时常见;等待一分钟或提高 extra.rate_limit
没有评论发布gh 未安装、不在 PATH 中或未认证(gh auth login
Agent 运行但无评论检查 Gateway 日志——如果 Agent 输出为空或仅为 "SKIP",仍会尝试投递
端口被占用修改 config.yaml 中的 extra.port
Agent 运行但仅审查 PR 描述prompt 中缺少 gh pr diff 指令——diff 不在 Webhook 载荷中
看不到 ping 事件被忽略的事件仅在 DEBUG 日志级别返回 {"status":"ignored","event":"ping"}——检查 GitHub 的投递日志(仓库 → Settings → Webhooks → 对应 Webhook → Recent Deliveries)

GitHub 的 Recent Deliveries 标签页(仓库 → Settings → Webhooks → 对应 Webhook)显示每次投递的精确请求头、载荷、HTTP 状态和响应体。这是无需查看服务器日志即可诊断故障的最快方式。


完整配置参考

yaml
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: []            # 第一个匹配的 Skill 被加载(仅一个)
          deliver: "log"        # log | github_comment | telegram | discord | slack | signal | sms
          deliver_extra: {}     # github_comment 用 repo + pr_number;其他用 chat_id

下一步

分享: