Claude Code Hooks設定:exit 1・2の違いと止まらない原因
このトピックを深掘りする
SaaSの導入・連携
AIに仕事の続きを渡すために、会議の原文、未完了の宿題、確認できない項目をどう残すか。導入判断と連携実装を掘り下げる。
全9本中 4 本目。
次に読む記事
Dub conversion tracking:lead・sale APIとStripeの顧客対応
クリックIDを登録・売上へ対応させるDubの実装ガイド。Businessの利用条件、leadとsaleの必須項目、USD・JPYの金額、invoiceId、Stripe metadataを公式資料と資格情報なしのJSON生成例で確認する。
Dub webhook署名検証:raw bodyと再送の境界をNode.jsで確認する
登録通知を受け取り、raw bodyのHMAC-SHA256でDub-Signatureを検証する。lead.createdから次の作業に必要な項目を取り出す。資格情報なしのNode.js/Bun例で改変・JSON再整形・再受理を確かめ、Businessの条件と未検証の保存・実配信を示す。
Slackのhuddleを録り、毎晩22時にAIへ渡す:Circlebackの設定6か所
AIに仕事の続きを渡すためのCircleback運用記録。Slack huddle、22時の同期、元の発言と未完了ToDo、採用する条件を具体化。運用経験・公式仕様・ローカル試験の根拠も示す。
シリーズ全体の流れを見ながら、次に読む記事へ進めます。 初めての方はホームへ →
Claude Codeのhookが exit 1 を返したのに、ツールの処理が続く。まず、失敗した検査の終了コードがhook全体の終了コードとして返っているかを確認します。そのうえで、イベントが実行前の PreToolUse か、実行後の PostToolUse かを分けます。
2026年10月25日に公式Hooksリファレンスを確認しました。以下の表と配布モデルは、stdoutが空の同期command hookを対象にします。Claude Code本体を動かした試験ではありません。
exit 1とexit 2は、設定によって結果が変わる
PreToolUse のexit 2は、実行前のツール呼び出しを止めます。理由はstderrで渡します。exit 1や127を同じ意味で使うと、onFailure の設定によっては通常の許可フローへ戻ります。この「戻る」は実行許可を与えることではなく、その後も通常の権限判断が残ります。
| 終了コード | PreToolUse / continue | PreToolUse / block | PostToolUse / continue・block |
|---|---|---|---|
| 0 | 通常の許可フロー | 通常の許可フロー | 実行済み |
| 1 | 通常の許可フロー | 呼び出しを止める | 実行済み |
| 2 | 呼び出しを止める | 呼び出しを止める | 実行済み |
| 127 | 通常の許可フロー | 呼び出しを止める | 実行済み |
PostToolUseのexit 2では、stderrがClaudeへ渡ります。ただし、ツールの操作はすでに終わっています。検査結果を伝える用途と、実行前に止める用途を分けてください。表はツールを止められるかだけを比較しており、エラー表示の違いは再現しません。
Hooks設定ではonFailureを明示する
onFailure: "block" はClaude Code v2.1.295以降が必要です。WindowsでGit Bashがなく、shell形式のhookがonFailure未設定の場合には、失敗が呼び出しを止める例外があります。配布モデルでは未設定を受け付けず、continueかblockを明示して、この既定値の違いを除いています。失敗時の設定とWindowsの条件を使用中のバージョンと照合してください。
次は .claude/settings.json の構造を示す説明用設定です。Bashに一致する呼び出しを毎回止めるため、普段の設定へそのまま追加するものではありません。この記事の実験では設定を作成・適用しません。
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "node",
"args": ["-e", "process.exit(2)"],
"onFailure": "block"
}]
}]
}
}matcher、イベント名、hookの最後の終了コードを順に確認すると、「呼ばれていない」「呼ばれたが止めない」「実行後に気づいた」を切り分けられます。
後続の成功で検査の終了コードを失う
検査が2で終了しても、ラッパーがその後の成功を終了コードとして返せば、呼び出し元へ届くのは0です。次のPOSIX shell例は固定したNodeコードだけを使います。先頭の検査は必ず2、後続処理は必ず0になります。
# 終了コードを失う例。全体は0。
node -e 'process.exit(2)'
node -e 'process.exit(0)'失敗した時点で終了すれば、後続処理へ進まず2を返せます。|| exit $? はその直前の失敗を返すPOSIX shellの書き方です。PowerShellへそのまま移す例ではありません。
node -e 'process.exit(2)' || exit $?
node -e 'process.exit(0)'通信しないデモで16通りを比較する
判定関数 hook-exit.mjsと実行例 hook-exit-demo.mjsを同じフォルダーへ保存します。Node.jsが必要です。Bunから起動する場合も、子プロセスにはNode.jsを使います。
node hook-exit-demo.mjs
# または、BunとNode.jsがある環境で
bun hook-exit-demo.mjsデモは固定した内部コードでexit 0・1・2・127を観測します。127も明示的に返した合成値で、実行ファイルの欠落は再現していません。2イベント×4終了コード×2設定の16通りを説明モデルへ渡し、終了コードを保つラッパーと失うラッパーも比較します。引数をコマンドとして実行せず、通信や設定ファイルの読み書き、AIの呼び出しは行いません。
出力の observedExits は [0, 1, 2, 127]、wrappers は {"preserved": 2, "swallowed": 0} になります。ラッパーの比較は、POSIX shellへ依存せずNodeの子プロセスで同じ順序を再現します。normal-permission-flow・block・already-ran は説明用の名前で、Claude Codeのログ文言ではありません。
JSONやタイムアウトへ表を広げない
有効なJSONをstdoutへ出すhookは別に確認してください。現行の公式資料では、非0の終了コードでも有効なJSONを読みます。exit 2のイベントごとの阻止効果は残り、ほかの非0ではJSONが判断を決める場合があります。ただし、失敗時にblockする設定では扱いが異なります。構造化した判断には、公式が示すexit 0と一つのJSONオブジェクトを使う方式を参照してください。
このモデルは公式仕様の解釈で、JSONの解析、timeout、起動失敗、async、ほかのイベントを検証しません。実行結果からClaude Code本体の挙動や保護の保証は導けません。次の作業へは、イベント名・明示したonFailure・最終終了コード・未確認条件を残します。コンテキスト引き継ぎの記録に添え、接続先の設定も疑わしい場合はMCP設定のスコープを確認できます。
よくある質問
- Q. Claude Code Hooksでexit 1が処理を止めないのはなぜ?
- 空stdoutの同期PreToolUseでonFailureがcontinueなら、exit 1は通常の許可フローへ戻ります。これは実行許可ではありません。exit 2は呼び出しを止めます。block設定ではexit 1も止めます。
- Q. PostToolUseのexit 2でツールを取り消せますか?
- 取り消せません。ツールは実行済みです。exit 2はstderrをClaudeへ渡します。実行前に止める検査はPreToolUseで扱います。
- Q. onFailureを省略しても同じですか?
- WindowsでGit Bashがないshell形式のhookには、未設定時に失敗が呼び出しを止める例外があります。例ではcontinueかblockを明示します。blockにはv2.1.295以降が必要です。
- Q. 終了コードが非0ならJSONは無視されますか?
- 現行の公式資料は非0でも有効なJSONを読みます。exit 2のイベントごとの阻止効果は残り、ほかの非0は失敗時の設定にも依存します。配布モデルは空stdoutだけを扱い、JSONを検証しません。
- Q. 検査はexit 2なのにhook全体が0になるのはなぜ?
- ラッパーが後続の成功を返すと、検査の終了コードが失われます。POSIX shellでは検査の直後に || exit $? を置く例があります。配布デモは固定したNodeコードで保持2・消失0を比較します。