ankuro.dev
← ブログ一覧に戻る
【Claude Code中級編 #15】Hooksで通知を飛ばす——Stopフック × Webhookで Discord 通知を実装する
2026-04-25#Claude Code#AI#Hooks#Webhook#Discord#通知

【Claude Code中級編 #15】Hooksで通知を飛ばす——Stopフック × Webhookで Discord 通知を実装する

長時間タスクの完了・テスト失敗・デプロイの終了など、Claude Codeのイベントを外部サービスに通知したい場面は多い。Claude Code には専用の通知機能があるわけではなく、Hooks(第2〜3回で扱った仕組み)の Stop / StopFailure / Notification イベントから Webhook を叩く形で実装する。

この記事でわかること:

  • 通知発信に使える Hooks イベント(Stop / StopFailure / Notification)の役割
  • settings.json から Discord Webhook を叩く最小実装
  • Discord・Slack への通知実装と色分けデザイン
  • 通知疲労を避けるフィルタリング設計

なぜ通知を外に飛ばしたいか

Claude Code で以下のような場面を想像してみる。

- 30分かかるテストを「終わったら知らせて」と任せた
- /loop で定期実行しているタスクが失敗した
- チームでClaude Codeを使っていて、誰が何を完了したか共有したい

ターミナルを見続けていれば気づけるが、現実は他の作業をしている。通知を外に飛ばす仕組みが要る。

メールでも良いがリアルタイム性が低い。チームで使うなら Discord・Slack のチャンネルに飛ばすのが一番効率的になる。Claude Code 単体ではこの「外部通知」の機能を持たないため、Hooks から curl で Webhook を叩く形が王道になる。


通知に使う Hooks イベント

第2〜3回でフックの基本(PreToolUse / PostToolUse など)を扱ったが、通知発信で主に使うのは別系統のイベント。

イベント 発火タイミング
Stop Claude が応答を終えたとき(タスク正常終了)
StopFailure API エラーなどで会話ターンが異常終了したとき
Notification Claude Code が通知を発したとき(権限要求やidle等)

StopStopFailure を分けることで、成功時と失敗時で別チャンネルに飛ばすこともできる。

Hooks のおさらい

.claude/settings.jsonhooks フィールドにイベント名と実行コマンドを書く。コマンド側には stdin で JSON が渡されるので、必要なら jq で内容を取り出して使える。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [{ "type": "command", "command": "echo done" }]
      }
    ]
  }
}

基本の Webhook 設定

① Discord Webhook URL の取得

Discord のチャンネル設定 → 「連携サービス」→「ウェブフック」→「新しいウェブフック」で作成。コピーした URL を控えておく。

② 最小実装(Stop で Discord に飛ばす)

.claude/settings.jsonStop フックを追加し、curl で Discord に POST するだけ。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "curl -s -H 'Content-Type: application/json' -d '{\"content\":\"✅ Claude Code のタスクが完了しました\"}' https://discord.com/api/webhooks/xxxxxxx/yyyyyy"
          }
        ]
      }
    ]
  }
}

これで Claude が応答を終えるたびに Discord に「✅ タスク完了」が届く。

③ stdin の JSON を活かす

Hooks のコマンドには stdin から JSON が渡る(session_id / transcript_path / cwd / hook_event_name など)。シェルから jq で取り出して通知本文に組み込める。

"command": "jq -Rsc --arg cwd \"$PWD\" '{content: (\"✅ \\($cwd) のタスクが完了\")}' < /dev/null | curl -s -H 'Content-Type: application/json' -d @- https://discord.com/api/webhooks/xxx/yyy"

長くなる場合はインラインで書かず、シェルスクリプトに切り出して command から呼び出す方が読みやすい。

"command": ".claude/scripts/notify-discord.sh"

Discord Embed で色分けする

Discord Webhook は embeds 配列を受け取り、color(10進数)で左の縦バーの色を変えられる。成功は緑・失敗は赤に出し分けると、チャンネルを見るだけで状態が一目で分かる。

.claude/scripts/notify-discord.sh

#!/usr/bin/env bash
set -e

EVENT="${1:-Stop}"  # Stop / StopFailure を引数で受け取る
WEBHOOK_URL="https://discord.com/api/webhooks/xxx/yyy"

if [ "$EVENT" = "StopFailure" ]; then
  TITLE="❌ タスク失敗"
  COLOR=15548997   # 赤
else
  TITLE="✅ タスク完了"
  COLOR=5763719    # 緑
fi

PAYLOAD=$(jq -nc \
  --arg title "$TITLE" \
  --arg cwd "$PWD" \
  --argjson color "$COLOR" \
  '{embeds:[{title:$title, description:$cwd, color:$color}]}')

curl -s -H "Content-Type: application/json" -d "$PAYLOAD" "$WEBHOOK_URL" >/dev/null

settings.json 側はイベントごとにスクリプトに引数を渡すだけ。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": ".claude/scripts/notify-discord.sh Stop"
          }
        ]
      }
    ],
    "StopFailure": [
      {
        "hooks": [
          {
            "type": "command",
            "command": ".claude/scripts/notify-discord.sh StopFailure"
          }
        ]
      }
    ]
  }
}

Slack 連携の実装(おまけ)

Slack も Incoming Webhook URL を発行する仕組みは同じ。ペイロード形式だけ Slack のフォーマットに合わせる。

PAYLOAD=$(jq -nc \
  --arg text "✅ $PWD のタスク完了" \
  '{text:$text, blocks:[{type:"section", text:{type:"mrkdwn", text:$text}}]}')

curl -s -H "Content-Type: application/json" -d "$PAYLOAD" \
  "https://hooks.slack.com/services/T00/B00/xxxxx"

ペイロード形式が違うだけで、フックから curl を叩く構造は同じ。サービスごとにペイロードを組み立てるシェルスクリプトを分けるのが基本パターンになる。


CI/CD イベントを通知する実例

テスト失敗時に Discord へ通知

StopFailure イベントを使えば、ターンが異常終了したときだけ通知できる。

シナリオ:
  Claude Code に「フルテストを走らせて」と指示
    ↓
  途中でテスト失敗 → API エラーで会話ターンが異常終了
    ↓
  StopFailure フック発火
    ↓
  Discord に「❌ タスク失敗」が即座に届く

開発者はターミナルを見ていなくても、Discord の通知でタスクの異常終了を把握できる。

なお、Claude が「テスト失敗」を文章で報告して正常終了した場合は Stop 側で発火する。「人間にとっての成功/失敗」と「Claude Code の Stop/StopFailure」は別物である点に注意。テスト結果に応じた通知を確実に出したいなら、テストコマンド側の終了コードでフックの中で分岐させる方が堅い。

長時間ジョブの完了通知

データ移行・バッチ処理など30分以上かかるジョブ専用の通知も組める。Hooks 側に「実行時間でフィルタする」機能はないので、フックスクリプトで開始時刻を保存しておき、Stop 時に差分を計算する。

# UserPromptSubmit フックで開始時刻を記録
echo "$(date +%s)" > /tmp/claude-task-start

# Stop フックで差分を計算し、しきい値超えだけ通知
START=$(cat /tmp/claude-task-start 2>/dev/null || echo 0)
NOW=$(date +%s)
DURATION=$(( NOW - START ))

if [ "$DURATION" -ge 600 ]; then
  curl -s -H 'Content-Type: application/json' \
    -d "{\"content\":\"⏱ 長時間タスク完了(${DURATION}秒)\"}" \
    "$WEBHOOK_URL"
fi

短い作業まで通知して埋もれるのを防げる。

`/loop` の定期実行と組み合わせる

定期実行ジョブ(/loop)と組み合わせると、結果通知も自動化できる。

1時間ごとにメトリクス収集 → 異常時はターン異常終了 → StopFailure → Discord に警告

人間が見続けなくても、異常時だけ通知が飛ぶ運用にできる。


カスタムペイロードの設計

通知に含めるべき情報

最低限、以下があると便利。

✅ イベント種別(成功・失敗・通知)
✅ 何が起きたか(メッセージ本文)
✅ どこで起きたか(cwd や git ブランチ名)
✅ いつ起きたか(タイムスタンプ)
✅ 失敗時はエラー要約

詰め込みすぎると逆に見づらくなるので、Discord Embed なら fieldsfooter を活用して整理する。

PAYLOAD=$(jq -nc \
  --arg title "✅ タスク完了" \
  --arg desc "$LAST_MESSAGE" \
  --arg cwd "$PWD" \
  --arg branch "$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '-')" \
  --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
  --argjson color 5763719 \
  '{embeds:[{
     title:$title,
     description:$desc,
     color:$color,
     fields:[
       {name:"ディレクトリ", value:$cwd, inline:true},
       {name:"ブランチ", value:$branch, inline:true}
     ],
     timestamp:$ts,
     footer:{text:"Claude Code"}
   }]}')

通知疲労を避ける

通知発信で怖いのは「通知が来すぎて誰も見なくなる」状態に陥ること。設計時に以下を意識する。

❌ よくないパターン
- すべての Stop で通知 → タスクが終わるたびに通知
- すべての Notification で通知 → 軽微な確認も全て飛ぶ

✅ 良いパターン
- StopFailure だけ → 異常終了時のみ通知
- 長時間タスクの Stop だけ → スクリプト側で duration フィルタ
- 重要チャンネルと雑多チャンネルを分離 → 緊急度で振り分け

「通知が来たら必ず見る」状態を維持できる粒度で設計する。


トラブルシューティング

Webhook URL が間違っていても気づきにくい

Hooks の curl は失敗してもデフォルトでは Claude の動作をブロックしない。通知失敗が静かに発生するので、初回設定後は Discord 側で受信できているか必ず確認する。スクリプトに set -ecurl --fail を入れておくと、フック実行ログにエラーが出る。

スクリプトに実行権限がない

.claude/scripts/notify-discord.sh を作ったら chmod +x を忘れない。実行権限がないと「command not found」相当でフックが落ちる。

stdin の JSON を読みたいのに読めない

フックコマンドには stdin で JSON が渡る。シェルスクリプトの先頭で INPUT="$(cat)" と読んで、後段で echo "$INPUT" | jq -r '.cwd' のように取り出す。cat を呼ばずに jq を直接動かすと、後続の処理に渡すべき stdin が消費される点に注意。

ペイロードの JSON 構文エラー

シェル変数を文字列補間で JSON に埋め込むと、改行や " が混ざって壊れやすい。jq -nc --arg ... でエンコードさせるのが安全。


まとめ

やりたいこと 設定
失敗時だけ通知 StopFailure フック + curl
成功・失敗を色分け フック内で分岐し、Discord Embed の color を変える
長時間タスクだけ通知 スクリプトで開始時刻を保存し、duration で分岐
Slack に飛ばす Webhook URL とペイロードを Slack 形式に変える
通知疲労を避ける StopFailure 中心・duration フィルタ・チャンネル分離

通知発信の本質は「Claude Code 本体の責務に通知ロジックを混ぜない」設計。Hooks をイベントの引き金、シェルスクリプトをペイロード組み立て役、と役割を分けると、長期運用でも破綻しない通知基盤が組める。次回は GitHub Actions と組み合わせて、CI/CD の自動化レベルを一段上げる方法を扱う。


第14回:Claude Codeに通る指示の書き方——プロンプト設計の基本第16回:GitHub ActionsにClaude Codeを組み込む——CI/CD自動化