Stop Hook を設定して、Claude がタスクを完了したらデスクトップ通知・Slack・Telegram で自動通知
Claude Code でタスクを実行すると、数分、場合によってはそれ以上かかることがある。モジュールのリファクタリング、テストスイートの実行、ファイルの一括処理——Claude に任せて、別のウィンドウで作業を進める。問題は、いつ終わったか分からないことだ。
戻ってみたら 10 分前に完了していた、なんてこともある。あるいは確認ダイアログで止まったまま、ずっとあなたの操作を待っていたり。
Stop Hook を使えば、Claude がタスクを完了したとき(または操作が必要なとき)に自動で通知を送れる。どのウィンドウにいても、どのデバイスにいても、通知が届く。
Claude Code には Stop イベントがある。Claude が停止するたびに発火するイベントで、タスク完了・エラーによる確認待ち・ユーザー入力待ちのいずれでもトリガーされる。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/notify.sh"
}
]
}
]
}
}
Hook は stdin から JSON を受け取る。中には stop_reason(停止理由)と stop_message(Claude の最後のメッセージ)が含まれていて、これを使って通知内容をカスタマイズできる。
stdin のデータ構造:
{
"session_id": "xxx",
"stop_reason": "end_turn",
"stop_message": "重構完成,所有測試通過。"
}
Linux は notify-send、macOS は osascript を使う。外部サービスは一切不要:
#!/bin/bash
input=$(cat)
reason=$(echo "$input" | jq -r '.stop_reason // "unknown"')
message=$(echo "$input" | jq -r '.stop_message // "Claude 已停止"' | head -c 200)
case "$(uname)" in
Linux)
notify-send "Claude Code" "$message" --urgency=normal
;;
Darwin)
osascript -e "display notification \"$message\" with title \"Claude Code\""
;;
esac
メリット:設定ゼロ、すぐ使える。
デメリット:通知を受け取れるのは今使っているマシンだけ。別のデバイスに移ると見逃す。
Slack の Incoming Webhook を使って、指定チャンネルや DM に通知を飛ばす。
事前準備:
1. Slack の App 管理ページ で App を作成
2. Incoming Webhooks を有効にして、Webhook URL を取得
3. 環境変数 SLACK_WEBHOOK_URL に URL を設定
#!/bin/bash
input=$(cat)
reason=$(echo "$input" | jq -r '.stop_reason // "unknown"')
message=$(echo "$input" | jq -r '.stop_message // "Claude 已停止"' | head -c 300)
webhook_url="${SLACK_WEBHOOK_URL}"
[[ -z "$webhook_url" ]] && exit 0
# 停止理由に応じて emoji を選択
case "$reason" in
end_turn) emoji="✅" ;;
user_input) emoji="⏳" ;;
*) emoji="🔔" ;;
esac
payload=$(jq -n \
--arg text "$emoji *Claude Code* | \`$reason\`
$message" \
'{text: $text}')
curl -s -X POST "$webhook_url" \
-H 'Content-Type: application/json' \
-d "$payload" > /dev/null 2>&1 &
末尾の & が重要だ。curl をバックグラウンドで実行して、Claude をブロックしないようにする。
Telegram Bot は個人利用に向いている。無料でリアルタイム、スマホですぐ受け取れる。
事前準備:
1. @BotFather で Bot を作成し、Token を取得
2. 自分の Chat ID を取得(Bot にメッセージを送ってから getUpdates API を呼ぶ)
3. 環境変数 TELEGRAM_BOT_TOKEN と TELEGRAM_CHAT_ID を設定
#!/bin/bash
input=$(cat)
reason=$(echo "$input" | jq -r '.stop_reason // "unknown"')
message=$(echo "$input" | jq -r '.stop_message // "Claude 已停止"' | head -c 300)
token="${TELEGRAM_BOT_TOKEN}"
chat_id="${TELEGRAM_CHAT_ID}"
[[ -z "$token" || -z "$chat_id" ]] && exit 0
text="🤖 *Claude Code*
状態:\`$reason\`
$message"
curl -s "https://api.telegram.org/bot${token}/sendMessage" \
-d chat_id="$chat_id" \
-d text="$text" \
-d parse_mode="Markdown" > /dev/null 2>&1 &
同じマシン上で別のウィンドウにいるなら、ポップアップよりも音で気づく方が確実なことがある:
#!/bin/bash
input=$(cat)
reason=$(echo "$input" | jq -r '.stop_reason // "unknown"')
case "$(uname)" in
Linux)
paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null &
;;
Darwin)
afplay /System/Library/Sounds/Glass.aiff &
;;
esac
他の方法と組み合わせるのもアリだ。ローカルで音を鳴らしつつ、リモートにプッシュ通知を送る、といった使い方ができる。
実運用では、複数の方法を 1 つのスクリプトにまとめるのがおすすめだ:
.claude/hooks/notify.sh:
#!/bin/bash
input=$(cat)
reason=$(echo "$input" | jq -r '.stop_reason // "unknown"')
message=$(echo "$input" | jq -r '.stop_message // "Claude 已停止"' | head -c 300)
# 停止理由に応じて emoji を選択
case "$reason" in
end_turn) emoji="✅"; title="任務完成" ;;
user_input) emoji="⏳"; title="等待輸入" ;;
*) emoji="🔔"; title="Claude 已停止" ;;
esac
# 1. デスクトップ通知(ローカル)
case "$(uname)" in
Linux) notify-send "Claude Code: $title" "$message" --urgency=normal ;;
Darwin) osascript -e "display notification \"$message\" with title \"Claude Code: $title\"" ;;
esac
# 2. サウンド通知
case "$(uname)" in
Linux) paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null & ;;
Darwin) afplay /System/Library/Sounds/Glass.aiff & ;;
esac
# 3. Slack(Webhook が設定されている場合)
if [[ -n "$SLACK_WEBHOOK_URL" ]]; then
payload=$(jq -n --arg text "$emoji *$title*
$message" '{text: $text}')
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "$payload" > /dev/null 2>&1 &
fi
# 4. Telegram(Bot が設定されている場合)
if [[ -n "$TELEGRAM_BOT_TOKEN" && -n "$TELEGRAM_CHAT_ID" ]]; then
curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d chat_id="$TELEGRAM_CHAT_ID" \
-d text="$emoji *$title*
$message" \
-d parse_mode="Markdown" > /dev/null 2>&1 &
fi
.claude/settings.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash .claude/hooks/notify.sh"
}
]
}
]
}
}
ネットワークリクエストは必ずバックグラウンドで
通知スクリプト内の curl には必ず & をつけてバックグラウンド実行にする。ネットワークが遅かったりタイムアウトしたりすると、Claude のセッション全体がブロックされてしまう。> /dev/null 2>&1 & をつけるのが定石だ。
Stop は「正常完了」とは限らない
stop_reason が end_turn なら大抵はタスク完了を意味するが、Claude が次に何をすべきか判断できなかった場合にも返されることがある。本当に完了したときだけ通知したければ、スクリプト内でフィルタすればいい:
[[ "$reason" != "end_turn" ]] && exit 0
通知内容は切り詰める
Claude の最後のメッセージは長くなることがある(コードブロックや大量の出力を含む場合など)。Slack や Telegram に送る前に head -c 300 で切り詰めて、メッセージが溢れるのを防ごう。
環境変数はグローバル設定に
Webhook URL や Bot Token をプロジェクトのリポジトリにコミットしてはいけない。~/.zshrc(または ~/.bashrc)に書くか、direnv で管理するのがおすすめだ。
設定が終わると、作業の流れが変わる:
ターミナルを見張る必要がない。ウィンドウを行ったり来たりしなくていい。Claude は Claude の仕事をして、あなたはあなたの仕事をする。終わったら教えてくれる。