用 Hooks 發任務完成通知:Claude 做完事自動通知你

設定 Stop Hook,讓 Claude 完成任務後透過桌面通知、Slack、Telegram 自動推送訊息


Claude Code 執行任務時常需要幾分鐘甚至更久。你讓它重構一個模組、跑完整個測試套件、處理一批檔案——然後切到別的視窗做其他事。問題是:你不知道它什麼時候做完了。

等你回來看,可能已經完成十分鐘了,也可能卡在一個確認對話框等你回應。

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 Webhook(推薦團隊使用)

用 Slack Incoming Webhook 把通知推到指定頻道或私訊。

事前準備:
1. 在 Slack 的 App 管理頁面 建立一個 App
2. 啟用 Incoming Webhooks,取得 Webhook URL
3. 把 URL 存到環境變數 SLACK_WEBHOOK_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

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 &

方案四:聲音提示(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

可以跟其他方案搭配使用——本機播放聲音 + 遠端推送通知。


完整設定:組合多種通知方式

實際使用上,建議把多種方式整合到一個腳本裡:

.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 做它的事,你做你的事,完成了通知你。