Claude Code の Stop hook は、メインの Claude エージェントが応答を終えるたびにコマンドを実行します。そのため「完了」通知の送信、結果のログ記録、チェックの実行に最適です。Claude を中断した場合は実行されず、API エラーで終わったターンでは代わりに StopFailure が発火します。hook は Claude の最後のメッセージを last_assistant_message で受け取り、decision: "block" と reason を返すことで、Claude を止めずに作業を続けさせることもできます。
01Stop が発火するとき・しないとき
Stop はタスク全体の完了時だけでなく、毎ターンの終わりに発火します。Claude Code には「タスク」という概念がなく、Claude が応答を止めてユーザーを待っていることしか分かりません。短い質問でも2時間のリファクタリングでも、Stop は1回です。
次の場合は発火しません。
- Esc を押すなどしてターンを中断したとき
- レート制限、過負荷、認証エラーなどの API エラーでターンが終了したとき。この場合 Claude Code は
StopFailureを発火し、マッチングに使えるerrorフィールド(rate_limit、overloaded、server_errorなど)が渡されます - サブエージェントが終了したとき。これは
SubagentStopです。
Stop は matcher に対応していないため、matcher を指定しても無視され、hook は毎ターン実行されます。
02最もシンプルな Stop hook
Claude の完了時にコマンドを実行するには、~/.claude/settings.json に次を追加します。この例は 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_messageこのターンにおける Claude の最終返答のテキストstop_hook_activeStop hook のブロックにより Claude がすでに作業を続行している場合は truebackground_tasksバックグラウンドシェルやサブエージェントなど、まだ実行中のタスクsession_crons/loop などによる、このセッションで予定されている再開transcript ファイルを読むのではなく last_assistant_message を使ってください。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 が作業を続けてしまいます。
05Claude に作業を続けさせる
Stop はブロック可能なイベントの1つです。停止をブロックすると、Claude は指定した理由を次の指示としてターンを続行します。方法は2つあります。
2で終了する。Claude は stderr の内容を理由として受け取ります。または0で終了し、JSON を出力する。
{
"decision": "block",
"reason": "The test suite is failing. Run npm test, fix the failures, then finish."
}
エラーの報告ではなくガイダンスを与える場合は、hookSpecificOutput.additionalContext でも会話を続行できます。この場合、transcript には hook エラーではなく Stop hook のフィードバックとして表示されます。
{
"hookSpecificOutput": {
"hookEventName": "Stop",
"additionalContext": "Please run the test suite before finishing"
}
}
無限ループを防ぐ
常にブロックする Stop hook では、Claude がいつまでも止まれません。次の2つの仕組みで保護されています。
- Stop hook によって Claude がすでに続行している場合、
stop_hook_activeはtrueになります。これをチェックして早めに終了してください。 - ツール呼び出しを挟まずに Stop hook が8回連続でブロックすると、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 はセッションの作業ディレクトリで実行されるため、npm test はプロジェクト内で実行されます。チェックは高速に保ちましょう。デフォルトではコマンド hook は最大 600 秒まで実行でき、その間 Claude は待機します。
スクリプトを書かずに Claude にゴールに向けて作業を続けさせたいだけなら、Claude Code 組み込みの /goal コマンドがセッション限定の Stop hook を自動で設定してくれます。
06失敗時にも通知する
API エラーでは Stop が発火しないため、「完了」通知だけではレート制限で Claude が中断したことに気づけません。それも知りたい場合は、同じスクリプトを StopFailure にも登録してください。出力と終了コードは無視されるので、できるのは通知かログ記録だけです。StopFailure では、last_assistant_message に "API Error: Rate limit reached" のようなエラーテキストが入ります。
07Claude Code Notifier の位置づけ
離席中に Claude の完了を知るには、push サービスを呼び出す Stop hook で十分です。自分で構築したい場合は、ntfy ガイドから始めるのがおすすめです。
Claude Code Notifier も同じ Stop hook を使いますが、違いが 1 つあります。返信を送り返せることです。無料のコマンドラインツール ccn は Mac、Windows、Linux で動作し、単体でデスクトップ通知を表示します。iPhone または Android アプリとペアリングすれば、ターンが終わった瞬間に「Claude has finished」の push が届き、次の指示をスマートフォンから入力できます。ccn はそれを Stop hook から Claude への次の指示として返すため、キーボードの前にいなくてもセッションが続きます。直近 30 秒以内にキーボードで入力していた場合は、スマートフォンからの入力を待ちません。本ツールは独立したもので、Anthropic 製ではありません。このワークフローについては長いタスク中に席を離れる方法を、インストールについてはccn のページをご覧ください。
08よくある質問
Stop hook は Claude がタスク全体を終えたときに実行されますか?それとも返答のたびですか?
返答のたびです。Stop はリクエストの規模にかかわらず、メインエージェントが応答を終えるたびに発火します。まだ予定されている作業があるか知りたい場合は、入力の background_tasks と session_crons を確認してください。
Stop hook が発火しないのはなぜですか?
ターンを中断した(この場合 Stop はスキップされます)か、API エラーで終了した(代わりに StopFailure が発火します)可能性があります。それ以外の場合は、/hooks に hook が表示されているか、スクリプトに実行権限があるかを確認してください。その他の原因はトラブルシューティングチェックリストで扱っています。
Stop hook の実行後も Claude が作業を続けるのはなぜですか?
hook が終了コード 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 はメインエージェント用です。SubagentStop はサブエージェントの終了時に発火し、エージェントの種類でマッチし、同じブロック用フィールドに対応しています。「Claude has finished」通知は Stop に設定するのが適切です。