
【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等) |
Stop と StopFailure を分けることで、成功時と失敗時で別チャンネルに飛ばすこともできる。
Hooks のおさらい
.claude/settings.json の hooks フィールドにイベント名と実行コマンドを書く。コマンド側には stdin で JSON が渡されるので、必要なら jq で内容を取り出して使える。
{
"hooks": {
"Stop": [
{
"hooks": [{ "type": "command", "command": "echo done" }]
}
]
}
}
基本の Webhook 設定
① Discord Webhook URL の取得
Discord のチャンネル設定 → 「連携サービス」→「ウェブフック」→「新しいウェブフック」で作成。コピーした URL を控えておく。
② 最小実装(Stop で Discord に飛ばす)
.claude/settings.json に Stop フックを追加し、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 なら fields と footer を活用して整理する。
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 -e と curl --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自動化 →