Hooks でタスク完了通知を飛ばす:Claude が終わったら自動で教えてくれる

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": "重構完成,所有測試通過。"
}

方法1:デスクトップ通知(一番シンプル)

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

メリット:設定ゼロ、すぐ使える。
デメリット:通知を受け取れるのは今使っているマシンだけ。別のデバイスに移ると見逃す。


方法2:Slack Webhook(チームにおすすめ)

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 をブロックしないようにする。


方法3:Telegram Bot

Telegram Bot は個人利用に向いている。無料でリアルタイム、スマホですぐ受け取れる。

事前準備:
1. @BotFather で Bot を作成し、Token を取得
2. 自分の Chat ID を取得(Bot にメッセージを送ってから getUpdates API を呼ぶ)
3. 環境変数 TELEGRAM_BOT_TOKENTELEGRAM_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 &

方法4:サウンド通知(SSH 環境でも便利)

同じマシン上で別のウィンドウにいるなら、ポップアップよりも音で気づく方が確実なことがある:

#!/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_reasonend_turn なら大抵はタスク完了を意味するが、Claude が次に何をすべきか判断できなかった場合にも返されることがある。本当に完了したときだけ通知したければ、スクリプト内でフィルタすればいい:

[[ "$reason" != "end_turn" ]] && exit 0

通知内容は切り詰める

Claude の最後のメッセージは長くなることがある(コードブロックや大量の出力を含む場合など)。Slack や Telegram に送る前に head -c 300 で切り詰めて、メッセージが溢れるのを防ごう。

環境変数はグローバル設定に

Webhook URL や Bot Token をプロジェクトのリポジトリにコミットしてはいけない。~/.zshrc(または ~/.bashrc)に書くか、direnv で管理するのがおすすめだ。


使ってみると

設定が終わると、作業の流れが変わる:

  1. Claude にタスクを渡す
  2. 別のウィンドウやデバイスに移る
  3. Claude が完了 → 通知が届く
  4. 戻って結果を確認し、次のステップへ

ターミナルを見張る必要がない。ウィンドウを行ったり来たりしなくていい。Claude は Claude の仕事をして、あなたはあなたの仕事をする。終わったら教えてくれる。