Claude Code: 止めるフックは exit 2、入力は stdin の JSON。違反を起こして止まるのを見るまで「防げる」と書かない
ブロック用のフックが1か月何も止めていなかった例がある。原因は2つで、入力を環境変数から読んでいたことと、exit 1 で終わっていたこと。コマンドフックの入力は stdin に JSON で届き、操作を止めるのは exit 2 だけ。exit 1 は「止めないエラー」として素通りする。
どんな時に使う?
rm -rfや本番への接続などを PreToolUse フックで止めているつもりでいる- AI にフックを書かせ、そのまま「ブロック済み」と CLAUDE.md やチームに伝えようとしている
手順
- 入力は stdin から読む:
cmd=$(jq -r '.tool_input.command // empty') - 止めるときは理由を stderr に出して
exit 2:bash#!/bin/bash cmd=$(jq -r '.tool_input.command // empty') if echo "$cmd" | grep -q 'rm -rf'; then echo "rm -rf は禁止" >&2 exit 2 fi exit 0 - 実際に違反を起こしてみる: Claude に
rm -rf ./tmp-testのような禁止操作を頼み、ブロックされて stderr の理由が表示されるのを確認する - 確認できてから、ドキュメントやルールに「防いでいる」と書く。止まらないときは debug ログ(
claude --debugなど)でフックの終了コードを見る
注意点
exit 0と JSON のpermissionDecision: "deny"でも止められる。ただし JSON が壊れていたりスキーマに合わなかったりすると、止まらないエラー扱いで操作が通るexit 2は JSON のallowより強い。止めたいときに迷ったら exit 2- Stop・SubagentStop などでは、exit 2 でも stderr が「ファイルが無い」系だと止まらないエラー扱いになる。スクリプトのパス間違いに注意
- フックが落ちたら止めたい場合は Claude Code: hook に onFailure: "block" を付けて失敗時に通さない。ルールよりフックを選ぶ理由は Claude Code: 「〜したら〜して」はルールではなく hooks(settings.json)に書く
出典
- @devoctane 2026-10-29(1か月何も止めていなかったフック): @devoctane · 2026-10-29
- 公式ドキュメント(Hooks reference / Exit code output): code.claude.com