Webhook で GitHub の PR に自動でコメントする
目次
この案内では、Hermes Agent を GitHub につないで、プルリクエストの差分を取り、コードの変更を読み解き、コメントを書き込むところまでを自動にします。きっかけは webhook のイベントで、こちらから頼む必要はありません。
PR が開かれたり更新されたりすると、GitHub はあなたの Hermes に webhook の POST を送ります。Hermes は、gh の CLI で差分を取ってくるよう指示したプロンプトでエージェントを動かし、その返事が PR のスレッドに書き込まれます。
前提
- 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 # 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_comment は gh 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 を登録する
- 対象のリポジトリで Settings → Webhooks → Add webhook と進みます
- 次を入力します:
- 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 にチェック
- Add webhook を押します
GitHub は接続の確認のため、すぐに ping のイベントを送ってきます。これは安全に無視されます(ping は events の一覧に入っていません)。返るのは {"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 のイベントを、さまざまなアクションで送ってきます。opened、synchronize、reopened、closed、labeled などです。events の一覧は X-GitHub-Event ヘッダーの値で絞り込み、ルートごとの filters は action などペイロードの項目で絞り込めます。
ステップ 1 のプロンプトは、closed と labeled のときは早々に切り上げるようエージェントに指示することで、この点に対処しています。
> Jinja2 のような条件つきのひな形の書き方はありません。使えるのは {field} と {nested.field} の置き換えだけです。それ以外は、そのままエージェントに渡ります。
スキルでレビューの調子をそろえる
Hermes のスキル を読み込ませると、エージェントのレビューの人柄が一定になります。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}"> 補足: 読み込まれるのは、一覧のうち最初に見つかったスキル 1 つだけです。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 HookGitLab のペイロードの項目名は 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次はどうする
- cron で PR をレビューする — 定期的に PR を見に行く方式。外から届く窓口は要りません
- Webhook の一覧 — webhook の受け口の設定を、ひととおり載せています
- プラグインを作る — レビューの仕組みを、配れるプラグインにまとめます
- プロファイル — レビュー専用のプロファイルを、独自の記憶と設定で動かします