Hermes Agent Wiki 非公式・日本語wiki

Webhook で GitHub の PR に自動でコメントする

目次

この案内では、Hermes Agent を GitHub につないで、プルリクエストの差分を取り、コードの変更を読み解き、コメントを書き込むところまでを自動にします。きっかけは webhook のイベントで、こちらから頼む必要はありません。

PR が開かれたり更新されたりすると、GitHub はあなたの Hermes に webhook の POST を送ります。Hermes は、gh の CLI で差分を取ってくるよう指示したプロンプトでエージェントを動かし、その返事が PR のスレッドに書き込まれます。


前提

  • Hermes Agent がインストールされ、動いていること(hermes gateway
  • ゲートウェイを動かすホストに gh CLI が入っていて、認証が済んでいること(gh auth login
  • あなたの Hermes に外から届く URL があること(手元で動かしている場合は ngrok で手元を試す をご覧ください)
  • 対象の GitHub リポジトリの管理権限(webhook を扱うのに必要です)

ステップ 1 — 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 のペイロードから埋まります。
deliver github_commentgh pr comment で投稿します。log はゲートウェイのログに書くだけです。
deliver_extra.repo ペイロードから org/repo のような値に解決されます。
deliver_extra.pr_number ペイロードから PR の番号に解決されます。

ステップ 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? → Select individual events → Pull requests にチェック
  1. Add webhook を押します

GitHub は接続の確認のため、すぐに ping のイベントを送ってきます。これは安全に無視されます(pingevents の一覧に入っていません)。返るのは {"status": "ignored", "event": "ping"} です。記録されるのは DEBUG のレベルだけなので、既定のログのレベルでは画面に出てきません。


ステップ 4 — 試しに PR を開く

ブランチを作り、変更を push して、PR を開いてください。30〜90 秒ほど(PR の大きさとモデルによります)で、Hermes がレビューのコメントを書き込むはずです。

エージェントの進み具合をその場で追うには、こうします。

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

ngrok で手元を試す

Hermes を手元のノート PC で動かしているなら、ngrok で外から届くようにします。

ngrok http 8644

表示された https://...ngrok-free.app の URL を、GitHub の Payload URL に入れてください。ngrok の無料枠では、再起動するたびに URL が変わります。そのつど GitHub の webhook を更新することになります。有料の ngrok なら固定のドメインが使えます。

固定の内容を送るルートなら、curl だけで動作を試せます。GitHub のアカウントも本物の PR も要りません。

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"

特定のアクションだけに絞る

GitHub は pull_request のイベントを、さまざまなアクションで送ってきます。openedsynchronizereopenedclosedlabeled などです。events の一覧は X-GitHub-Event ヘッダーの値で絞り込み、ルートごとの filtersaction などペイロードの項目で絞り込めます。

ステップ 1 のプロンプトは、closedlabeled のときは早々に切り上げるようエージェントに指示することで、この点に対処しています。

> Jinja2 のような条件つきのひな形の書き方はありません。使えるのは {field}{nested.field} の置き換えだけです。それ以外は、そのままエージェントに渡ります。


スキルでレビューの調子をそろえる

Hermes のスキル を読み込ませると、エージェントのレビューの人柄が一定になります。config.yamlplatforms.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}"

> 補足: 読み込まれるのは、一覧のうち最初に見つかったスキル 1 つだけです。Hermes は複数のスキルを重ねません。以降のものは無視されます。


Slack や Discord に返事を送る

ルートの中の deliverdeliver_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-EventMerge Request HookPush HookPipeline Hook のような値を入れてきます。events にはヘッダーの値をそのまま書いてください。

events:
  - Merge Request Hook

GitLab のペイロードの項目名は GitHub とは違います。たとえば MR のタイトルは {object_attributes.title}、MR の番号は {object_attributes.iid} です。ペイロードの構造をいちばん手軽に知るには、webhook の設定にある GitLab の Test ボタンと Recent Deliveries のログを合わせて使います。あるいは、ルートの設定から prompt を省く手もあります。そうすると Hermes は、整形した JSON としてペイロード全体をそのままエージェントに渡すので、エージェントの返事(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 がエージェントの動きを操ろうとしてくることがあります。インターネットに向けて開くなら、ゲートウェイは隔離された環境(Docker、仮想マシン)で動かしてください。

うまくいかないとき

症状 確かめること
401 Invalid signature config.yaml の秘密の値が、GitHub の webhook の秘密の値と一致していません
404 Unknown route URL の中のルート名が、routes: のキーと一致していません
429 Rate limit exceeded ルートごとの毎分 30 回を超えました。GitHub の画面からテストのイベントを再送したときによく起きます。1 分待つか、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 側の配信のログ(リポジトリ → Settings → Webhooks → 対象の webhook → Recent Deliveries)を確かめてください

GitHub の Recent Deliveries のタブ(リポジトリ → Settings → Webhooks → 対象の webhook)には、配信ごとの実際のリクエストのヘッダー、ペイロード、HTTP のステータス、返された本文が並びます。サーバーのログに触らずに原因を突き止める、いちばん早い道です。


設定の一覧

platforms:
  webhook:
    enabled: true
    extra:
      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

次はどうする