每當 Claude 主 agent 完成回應,Claude Code 的 Stop hook 就會執行一個指令,因此很適合用來發送「完成」通知、記錄結果或執行檢查。你中斷 Claude 時它不會執行;若某個回合因 API 錯誤而結束,則會改為觸發 StopFailure。hook 會在 last_assistant_message 中收到 Claude 的最後一則訊息,也可以回傳 decision: "block" 並附上 reason,讓 Claude 繼續而不停止。
01Stop 何時會觸發、何時不會
Stop 會在每個回合結束時觸發,而不只是在整個任務完成時。Claude Code 並沒有「任務」的概念,它只知道 Claude 已停止回應、正在等你。一個簡短的問題會觸發一次 Stop,兩小時的重構也一樣只觸發一次。
以下情況不會觸發:
- 你按下 Esc 或以其他方式中斷該回合時;
- 回合因 API 錯誤(例如速率限制、過載或驗證失敗)而結束時。這些情況下 Claude Code 會觸發
StopFailure,並附上可供比對的error欄位(rate_limit、overloaded、server_error等); - subagent 完成時。那是
SubagentStop。
Stop 不支援 matcher,因此你加上的任何 matcher 都會被忽略,hook 會在每個回合執行。
02最簡單的 Stop hook
將以下內容加入 ~/.claude/settings.json,即可在 Claude 每次完成時執行指令。這個範例會在 macOS 上播放音效:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "afplay /System/Library/Sounds/Glass.aiff"
}
]
}
]
}
}
Mac、Windows 和 Linux 的桌面通知指令,請參閱如何在 Claude Code 完成時收到通知。本指南其餘部分將說明如何運用 hook 的輸入與輸出。
03hook 會收到什麼
除了共通欄位(session_id、transcript_path、cwd、permission_mode、hook_event_name)之外,Stop hook 還會收到:
last_assistant_messageClaude 在此回合最後一則回覆的文字stop_hook_active若 Claude 已因 Stop hook 阻擋而在繼續執行,則為 truebackground_tasks仍在執行中的任務,例如背景 shell 或 subagentsession_crons此工作階段中已排定的喚醒,例如來自 /loop請使用 last_assistant_message,不要去讀 transcript 檔案。transcript 是非同步寫入的,hook 執行時可能還沒寫入最後一則訊息。
background_tasks 和 session_crons 能告訴你工作階段是真的結束了,還是只是暫停、等背景工作再次喚醒。通知腳本可以先確認兩者皆為空,再顯示「完成」。
04在通知中顯示 Claude 的最後一則訊息
這個腳本(適用 macOS 和 Linux,需要 jq)會顯示 Claude 回覆的開頭,若仍有背景工作在執行則不發出通知:
#!/bin/bash
# ~/.claude/hooks/notify-done.sh
input=$(cat)
# Skip if background tasks are still in flight
busy=$(jq '(.background_tasks // []) | length' <<<"$input")
[ "$busy" -gt 0 ] && exit 0
msg=$(jq -r '(.last_assistant_message // "Claude has finished")
| gsub("\\s+"; " ") | .[0:140]' <<<"$input")
if command -v osascript >/dev/null 2>&1; then
osascript -e 'on run argv' \
-e 'display notification (item 1 of argv) with title "Claude has finished"' \
-e 'end run' "$msg"
elif command -v notify-send >/dev/null 2>&1; then
notify-send "Claude has finished" "$msg"
fi
exit 0
存檔後執行 chmod +x ~/.claude/hooks/notify-done.sh,再讓 hook 指向它:
{
"hooks": {
"Stop": [
{
"hooks": [{ "type": "command", "command": "~/.claude/hooks/notify-done.sh" }]
}
]
}
}
這個腳本一律以 0 結束。這點很重要:在 Stop 上,結束代碼 2 代表「不要停止」,所以通知腳本若意外以 2 結束,就會讓 Claude 一直工作下去。
05讓 Claude 繼續工作
Stop 是可以阻擋的事件之一。阻擋停止代表 Claude 會繼續這個回合,並把你提供的原因當作下一個指示。有兩種做法:
- 以
2結束,Claude 會將你的 stderr 當作原因;或 - 以
0結束並輸出 JSON:
{
"decision": "block",
"reason": "The test suite is failing. Run npm test, fix the failures, then finish."
}
如果 hook 是在提供指引而非回報錯誤,hookSpecificOutput.additionalContext 同樣能讓對話繼續,而 transcript 會將其標示為 Stop hook 回饋,而不是 hook 錯誤:
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "Please run the test suite before finishing"
}
}
避免無限迴圈
一個總是阻擋的 Stop hook 會讓 Claude 永遠停不下來。有兩道防線:
- 一旦 Claude 已因 Stop hook 而繼續執行,
stop_hook_active就會是true。檢查它並提早結束即可。 - 若 Stop hook 連續阻擋八次且中間沒有任何工具呼叫,Claude Code 仍會結束該回合。可透過
CLAUDE_CODE_STOP_HOOK_BLOCK_CAP環境變數提高這個上限。
以下這個 hook 會讓 Claude 修正失敗的測試一次,然後讓它停止:
#!/bin/bash
# ~/.claude/hooks/tests-must-pass.sh
input=$(cat)
[ "$(jq -r '.stop_hook_active' <<<"$input")" = "true" ] && exit 0
if ! npm test --silent >/dev/null 2>&1; then
jq -nc '{decision: "block", reason: "npm test is failing. Fix the failures, then finish."}'
fi
exit 0
Hook 會在 session 的工作目錄中執行,所以 npm test 會在你的專案裡跑。檢查要盡量快:command hook 預設最多可執行 600 秒,而 Claude 會一直等它跑完。
如果你只是想讓 Claude 朝某個目標持續工作、又不想寫腳本,Claude Code 內建的 /goal 指令會自動幫你設定一個僅限該 session 的 Stop hook。
06失敗時也發通知
由於 API 錯誤不會觸發 Stop,「完成」通知不會告訴你 Claude 因為 rate limit 而放棄了。如果想收到這類通知,就把同一支腳本也加到 StopFailure 底下。它的輸出和 exit code 都會被忽略,所以只能用來通知或記錄。在 StopFailure 中,last_assistant_message 會是錯誤訊息,例如 "API Error: Rate limit reached"。
07Claude Code Notifier 的角色
用 Stop hook 呼叫推播服務,是在你離開座位時得知 Claude 已完成的好方法。如果想自己動手做,可以從 ntfy 指南開始。
Claude Code Notifier 使用同樣的 Stop hook,差別只有一個:它可以回傳內容。它的免費命令列工具 ccn 可在 Mac、Windows 和 Linux 上執行,本身就能顯示桌面通知。配對 iPhone 或 Android App 後,每個回合一結束你就會收到「Claude 已完成」的推播,還能直接在手機上輸入下一個指令。ccn 會從 Stop hook 把它回傳為 Claude 的下一個指令,讓 session 在你不在鍵盤前時也能繼續。如果你在最近 30 秒內有打字,就不會等待手機。這是獨立開發的工具,並非 Anthropic 出品。這套流程請參考離開長時間任務,安裝方式請見 ccn 頁面。
08常見問題
Stop hook 是在 Claude 完成整個任務時執行,還是每次回覆後都會執行?
每次回覆後都會執行。只要主 agent 回應結束,不論請求大小,Stop 都會觸發。若需要知道是否還有排定的工作,可以檢查輸入中的 background_tasks 和 session_crons。
為什麼我的 Stop hook 沒有觸發?
可能是你中斷了該回合,這會略過 Stop;或是回合因 API 錯誤結束,這時觸發的是 StopFailure。除此之外,請確認 /hooks 有列出你的 hook,且腳本具有執行權限。其他情況請參考疑難排解清單。
為什麼我的 Stop hook 執行後 Claude 還在繼續工作?
你的 hook 擋下了停止動作,可能是以 exit code 2 結束,或輸出了 decision: "block"。請確保只負責通知的 hook 一律以 0 結束,而任何會阻擋的 hook 都要先檢查 stop_hook_active。
如何在 hook 中取得 Claude 的最終回答?
從 stdin 的 JSON 讀取 last_assistant_message,例如使用 jq -r '.last_assistant_message'。不要依賴 transcript_path:Stop 觸發時,該檔案可能還沒寫入最後一則訊息。
Stop 和 SubagentStop 有什麼差別?
Stop 針對主 agent。SubagentStop 會在 subagent 完成時觸發,依 agent 類型比對,並支援相同的阻擋欄位。「Claude 已完成」的通知應該放在 Stop。