Claude Codeのフックが効かない|黙って飛ばされる2条件
「検査のフックを書いたのに、止まってほしいところで止まらない」「ルールのファイルを置いたのに、守られていない気がする」——AIに作業を任せている人が、いちばん確かめにくい不安です。
結論から言うと、原因は「書いた内容が間違っている」側ではなく「その検査がそもそも呼ばれていない」側にあることがあります。2026年10月2日に公開された Claude Code v2.1.288 が直したのは、まさにその2件でした。
株式会社Fyveは、無人で動くジョブに検査を仕掛けて運用しています。この記事では公式の修正内容を逐語で確認し、私の環境を実際に数え直した結果と、「呼ばれたかどうか」を自分で確かめる手順までをまとめます。
結論——「フックを書いた」は「フックが走った」の証拠にならない
最初に、公式ドキュメントが明言している一文を引きます。フックの時間切れについての記述です。
A timed-out
command,http, ormcp_toolhook doesn't block the tool call. The call continues through the normal permission flow, so don't count on a stalled hook to act as a gate.(時間切れした
command・http・mcp_toolのフックは、道具の呼び出しを止めません。呼び出しは通常の許可の流れをそのまま進むので、止まったフックが関門として働くことを期待しないでください)
「止まったフックを関門として数えるな」と、提供元自身が書いています。つまり検査を書いたことと、検査が関門として働くことは別の話です。
私はこれを、自分の設定を数え直すまで分けて考えていませんでした。「検査を工程に埋めてある」という言い方を、そのまま「だから守られている」の意味で使っていました。
確かめるべきなのは、設定の中身ではなく呼ばれた記録
設定ファイルを開いて読み返しても、この種の問題は見つかりません。書いてある内容は正しいからです。問題は、書いてある内容が実行される経路に乗っていなかった、という点にあります。
この構造そのものは、AI以外の設定でも同じ形で起きます。一般的な話としては別記事にまとめました。

2026年10月2日の修正2件が、何を直したのか
v2.1.288 の変更履歴から、該当する2行を逐語で引きます。公式の変更履歴と GitHub のリリースページの2か所で同じ文を確認しました。
① 入れ子の CLAUDE.md とパス指定のルールは、書くときには読まれていなかった
Fixed path-scoped
.claude/rulesand nested CLAUDE.md files not loading when Write or Edit creates or changes a file in their scope (previously only Read loaded them)(パス指定の
.claude/rulesと入れ子の CLAUDE.md が、その範囲のファイルを Write や Edit で作成・変更するときに読み込まれない不具合を修正しました。以前は Read だけが読み込んでいました)
注目すべきは括弧の中です。「previously only Read loaded them」——読むときだけ読み込まれていた。書くとき、つまりファイルを新しく作るときや書き換えるときには、その場所に置いたルールが読み込まれていなかったということです。
ルールを置く動機は、たいてい「このフォルダのファイルを書くときに、この決まりを守ってほしい」です。その動機といちばん食い違う挙動だった、と読めます。
② PreToolUse と PermissionRequest は、照合に失敗すると飛ばされていた
Fixed PreToolUse and PermissionRequest hooks being skipped when matching them failed or the tool's input could not be serialized to JSON; the call is now blocked
(PreToolUse と PermissionRequest のフックが、照合に失敗したとき、または道具の入力を JSON に変換できなかったときに飛ばされる不具合を修正しました。現在はその呼び出しを止めるようになっています)
ここで大事なのは、条件が2つに限定されていることです。①照合に失敗したとき ②道具の入力を JSON に変換できなかったとき。この2つの場合に、黙って飛ばされていました。
「フックは効いていなかった」と一般化すると誤りになります。正しくは「この2つの場合に、黙って飛ばされていた」です。そして修正後の挙動も「飛ばさずに実行する」ではなく「その呼び出し自体を止める」——判断できないなら通さない、という設計に変わりました。
日付について1点だけ
この版の日付は 2026年10月2日です。公式の変更履歴が「October 2, 2026」と明記しており、前後の版も2026年9〜10月で連続しています。GitHub のリリースページ側の表示を読み取ったときに年が違う値で返ってくる場合がありますが、採るべきは公式変更履歴の2026年です。

自分の環境を数えたら、半分は無関係で、半分は直撃だった
ここが、この記事でいちばん書きたかった部分です。私は最初、①の修正を「ルールを68本置いていたのに、書くときには読まれていなかった」という話として書こうとしていました。
実際に数え直したら、それは私の環境については誤りでした。
ルール68本のうち、パス指定のものは0本だった
①の対象は「path-scoped .claude/rules」です。公式ドキュメントは、パス指定のルールが読み込まれる条件をこう説明しています。
Fires when a
CLAUDE.mdor.claude/rules/*.mdfile is loaded into context. This event fires at session start for eagerly-loaded files and again later when files are lazily loaded, for example when Claude accesses a subdirectory that contains a nestedCLAUDE.mdor when conditional rules withpaths:frontmatter match.(
CLAUDE.mdまたは.claude/rules/*.mdのファイルが文脈に読み込まれたときに発火します。先に読み込まれるファイルについてはセッション開始時に発火し、遅延して読み込まれるファイルについては後から再度発火します。たとえば入れ子のCLAUDE.mdを含むサブディレクトリにアクセスしたとき、またはpaths:のフロントマターを持つ条件付きルールが一致したときです)
つまりパス指定のルールとは、paths: のフロントマターを持つルールです。そこで自分の稼働中のルールを全件走査しました。
- 稼働中のルールファイル:68本(雛形と終了案件を含めると128本)
- そのうち
paths:のフロントマターを持つもの:0本 - そもそもフロントマターの区切り行を持つもの:0本
1本もありませんでした。私のルールはすべてセッション開始時に一括で読み込まれる側で、パス指定ではない。①の「ルール」側は、私の環境には当たっていません。
種として受け取ったときの要約をそのまま書いていたら、自分の設定について事実と違うことを公開していました。
当たっていたのは、入れ子の CLAUDE.md 87本の側
一方、①のもう半分は直撃でした。入れ子の CLAUDE.md、つまりルート以外に置いた CLAUDE.md の数を数えた結果です。
- 稼働中の CLAUDE.md:88本(雛形と終了案件を含めると138本)
- うちルート直下の1本を除いた入れ子は87本
- 深さ別:深さ1が9本、深さ2が45本、深さ3が25本、深さ4が6本、深さ5と6が各1本
深さ2が最も多く45本。ここは「各プロジェクトのフォルダに、そのプロジェクト固有の決まりを置く」という使い方をしている層です。そのフォルダのファイルを書き換えるときにこそ効いてほしいもので、修正前はそこで読み込まれていませんでした。

おまけ——1回目の集計は36本多かった
最初にルールを数えたとき、68本ではなく104本という数字が出ました。差の36本は、あるスキルが同梱している rules/ という名前のフォルダの中身でした。
スキルが内部で持つ rules/ と、パス指定のルールを置く .claude/rules/ は、フォルダ名が同じで役割が違う。名前で拾うと混ざります。自分の環境を数えるときにも同じ取り違えが起きる、という小さな実例でした。
自作の安全装置は、呼ばれなかった場合を守れない
次に②の側です。私のフックは7本で、内訳を実測するとこうなっていました。
イベント | 本数 | 照合の指定 | 時間切れの設定 |
|---|---|---|---|
PreToolUse(道具を使う前) | 3本 | シェル実行を対象 | 90秒・90秒・300秒 |
PostToolUse(ファイルを書いた後) | 2本 | 書き込みと編集を対象 | 未指定・90秒 |
Stop(会話が終わったとき) | 2本 | 指定なし | 未指定・60秒 |
②の対象は PreToolUse と PermissionRequest です。私は PermissionRequest を1本も持っていないので、効くのはPreToolUse の3本ということになります。
そしてこの7本は、すべて同じ書き方で包まれています。「検査のスクリプトが見つからなければ、入力を捨てて黙って成功で抜ける」という自作の安全装置です。
sh -c 'H="<検査スクリプトのパス>"; [ -f "$H" ] || { cat >/dev/null; exit 0; }; python3 "$H"'実際にスクリプトが存在しない状態でこの1行を走らせて、終了コードを確かめました。結果は 0——素通りです。意図どおりに動いています。無人で回す仕組みでは、検査が1本欠けたせいで本体が止まるのが最悪なので、これは納得して選んだ書き方でした。
ただし、この装置が走るのはフックが呼ばれた後だけ
問題はここです。この安全装置はフックが実行するコマンドの中に書かれています。ということは、フックそのものが呼ばれなければ、この1行は1文字も走りません。
②が直したのは、まさに「呼ばれない場合」でした。照合に失敗したら飛ばされる。飛ばされたら、コマンドは起動しない。コマンドが起動しなければ、その中の安全装置も動かない。自作ガードでは原理的に埋まらない穴だった、ということになります。
なお「自分の検査が外側にあるか内側にあるか」という別の切り口については、拡張の仕組みの話として先に書いています。あちらは検査がどこで動くかの記事で、この記事はそもそも呼ばれたのかの記事です。
時間切れと終了コード——もう2つの落とし穴
②とは別に、公式ドキュメントを読むと「書いたのに関門にならない」経路がさらに2つあります。どちらも版の新旧とは関係なく、いまも仕様です。
落とし穴1:時間切れしたフックは、出力を捨てられる
Claude Code cancels a
command,http, ormcp_toolhook that reaches itstimeout, discarding the hook's output, so on most events a timed-out hook renders no decision.(Claude Code は
timeoutに達したcommand・http・mcp_toolのフックを打ち切り、そのフックの出力を破棄します。そのためほとんどのイベントで、時間切れしたフックは何の判断も下しません)
時間切れの既定値は command・http・mcp_tool が 600秒です(ユーザー入力時やモデル切替の前後は30秒、メッセージ表示時は10秒に下げられます)。
私の7本のうち2本は時間切れを指定していません=既定の600秒です。そして PreToolUse の1本は300秒を指定しています。重い検査なので長めに取ったのですが、冒頭の逐語と組み合わせると意味が変わります。この検査が300秒を使い切った場合、止まるのではなく通常の許可の流れに合流して進むのです。
落とし穴2:終了コード1では止まらない
Without valid JSON on stdout, Claude Code treats exit code 1 as a non-blocking error and proceeds with the action, even though 1 is the conventional Unix failure code.
(標準出力に妥当な JSON が無い場合、Claude Code は終了コード1を止めないエラーとして扱い、動作をそのまま進めます。1 は Unix で慣例的な失敗の終了コードであるにもかかわらずです)
止めるのは終了コード2だけです。
Exit 2 means a blocking error. On events that can block, exit 2 blocks whether or not you print JSON: even a JSON
permissionDecisionof"allow"can't override it.(終了コード2は、止めるエラーを意味します。止められるイベントでは、JSON を出力するかどうかに関わらず終了コード2が止めます。JSON で
permissionDecisionを"allow"にしていても上書きできません)
検査スクリプトが途中でエラー終了すると、慣例どおり1が返ります。その1は止めません。「検査が失敗したのだから当然止まるだろう」という期待が、ここで外れます。
おまけ:照合の指定は、対応していないイベントでは黙って無視される
If you add a
matcherfield to an event without matcher support, it is silently ignored.(照合に対応していないイベントに
matcherの項目を足した場合、それは黙って無視されます)
私の Stop の2本は照合の指定を持っていません。これは正しい状態ですが、仮に書いていたら、エラーも警告も出ないまま無視されていたことになります。「書いたのに無視される」の3つめの経路です。
照合の書き方が1文字違うだけで効かなくなる話は、許可のルールの側でも同じ構造で起きます。こちらは別記事にまとめています。
呼ばれたかどうかを確かめる方法がある——InstructionsLoaded
ここまでは「確かめにくい」という話でしたが、確かめる手段は公式に用意されています。InstructionsLoaded というイベントです。
先に引いたとおり、これは CLAUDE.md や .claude/rules/*.md が文脈に読み込まれたときに発火します。しかも、何がきっかけで読み込まれたのかを一緒に受け取れます。
受け取れる項目 | 内容 |
|---|---|
| 読み込まれた指示ファイルの絶対パス |
| ファイルの範囲: |
| 読み込まれた理由: |
|
|
| どのファイルへのアクセスがこの読み込みを引き起こしたか(遅延読み込みのとき) |
| このファイルを読み込んだ親の指示ファイル( |
この load_reason が効きます。入れ子の CLAUDE.md が読み込まれたときの理由は nested_traversal です。つまりサブディレクトリのファイルを Write だけで書き換えて、nested_traversal の記録が出るかどうかを見れば、①が自分の環境で直っているかを目で確かめられます。
そして v2.1.288 には、このイベント自体に手が入った行もあります。
Fixed the InstructionsLoaded hook omitting agent_id and agent_type when a subagent's file access loads a rule or nested CLAUDE.md; rules and nested CLAUDE.md files loaded on file access now also report effort
(下位エージェントのファイルアクセスがルールや入れ子の CLAUDE.md を読み込んだときに、InstructionsLoaded のフックが agent_id と agent_type を落としていた不具合を修正しました。ファイルアクセスで読み込まれたルールと入れ子の CLAUDE.md は、effort も報告するようになりました)
同じ版で、①の読み込み自体と、その読み込みを観測する経路の両方が直っている。修正と、修正を確かめる手段が同時に来ている構図です。
「N hooks ran」は、当時は数え間違えていた
では以前は確かめられなかったのか。ここで前日の v2.1.287(2026年10月1日)に、気になる1行があります。
Fixed the transcript's "N hooks ran" summary and the verbose debug log's matched-hooks count including Claude Code's internal callbacks, so one configured hook no longer shows as two
(会話記録の「N hooks ran」の要約と、詳細デバッグログの照合済みフック数が、Claude Code 内部のコールバックを含めて数えていた不具合を修正しました。設定した1本のフックが2本として表示されることはなくなりました)
「いくつのフックが走ったか」を確かめようとして画面の表示を見ると、その数が実際より多く出ていた。設定した1本が2本に見えていたわけです。
これは地味ですが、効き方が厳しい種類の不具合です。②で1本が飛ばされていたとしても、表示が内部のコールバックを足して多めに数えていれば、本数を見ただけでは欠けに気づけません。確かめる手段の側が先に狂っていた、ということになります。
3つの版を並べると、同じ層の修正が並んでいた
2026年9月30日から10月2日までの3版を通して見ると、「指示が読み込まれる/検査が呼ばれる/それが数えられる」という層の修正が集まっています。逐語で確認できたものを並べます。
版(日付) | 修正の要点 |
|---|---|
2.1.288(10/2) | パス指定ルールと入れ子 CLAUDE.md が Write / Edit で読み込まれない |
2.1.288(10/2) | PreToolUse と PermissionRequest が照合失敗・JSON 変換失敗で飛ばされる |
2.1.288(10/2) | InstructionsLoaded が下位エージェント経由で agent_id / agent_type を落とす |
2.1.288(10/2) | 構造化出力を拒むゲートウェイの背後で、プロンプト系フックが失敗する |
2.1.288(10/2) | PreToolUse の承認について、計測データが source / decision を |
2.1.287(10/1) | 「N hooks ran」と照合済みフック数が内部コールバックを含めて数える |
2.1.287(10/1) | フォルダの CLAUDE.md が、再開後や圧縮後に二重に添付される |
2.1.286(9/30) | 道具やフックが文字列以外(オブジェクト・数値・真偽値)を返した後に API 400 エラーになる |
2.1.286(9/30) | 作業用コピーで隔離した下位エージェントが、最初のファイル読み込みでプロジェクトの CLAUDE.md を二重に読み込む |
読み込まれない、二重に読み込まれる、飛ばされる、数え間違える、報告されない。方向はばらばらですが、どれも「指示と検査が、期待した回数だけ効いているか」の層です。3日間で9件が並ぶのは、この層がもともと確かめにくいことの裏返しだと受け取っています。
ちなみに同じ版には、一覧を見やすくする変更も入っています。
Changed
/hooksto open on one list of your configured hooks grouped by event, so viewing a hook takes one Enter instead of three(
/hooksが、設定済みのフックをイベントごとにまとめた1つの一覧で開くようになりました。フックを1つ見るのに Enter が3回ではなく1回で済みます)
自分の検査を棚卸しする入口はここです。
今日からやる点検5手順
ここは正直に書きますが、私の手元の版は 2.1.209 です。①も②も入っていません。だから「直ったのを確かめた」とは書けません。書けるのは「どれだけ晒されていたかを数えた」と「確かめる手順はこう組める」までです。
① 版を確かめる
claude --version を実行します。2.1.288 より前なら、①と②は自分の環境に残っています。私の手元は 2.1.209 のままで、①も②もまだ試せていません。まずここの差を知るところからです。
② 自分の検査を数える
/hooks でイベントごとの一覧を開き、本数と内訳を書き出します。設定ファイルを直接走査しても構いません。私はこれで7本と、PreToolUse が3本であることを確定させました。②の対象は PreToolUse と PermissionRequest だけなので、ここで対象が0本なら②は無関係だと分かります。
③ ルールに paths: があるか見る
①の「ルール」側が自分に当たるかは、paths: のフロントマターを持つルールがあるかで決まります。1本も無ければ、その半分は無関係です。私はこれで、書こうとしていた内容を1つ取り下げました。入れ子の CLAUDE.md はこれとは別枠なので、両方を分けて数えます。
④ InstructionsLoaded で読み込みの記録を取る
file_path と load_reason、それに trigger_file_path を書き出すだけのフックを置きます。そのうえでサブディレクトリのファイルをWrite だけで変更し、nested_traversal の記録が出るかを見ます。出れば読み込まれており、出なければ読み込まれていません。設定を読み返すのではなく、記録を数えて判断する形にします。
⑤ 時間切れと終了コードを見直す
検査ごとに、時間切れの値と、失敗したときに返す終了コードを確かめます。見るべき点は2つです。時間切れを指定していなければ既定の600秒であること。そして止めたいなら終了コード2を返す必要があること——1では進んでしまいます。
取り違えやすい3点
「ルールを置いたから、守られている」
置き場所によって、読み込まれる条件が違います。セッション開始時に一括で読み込まれるものと、そのフォルダに触ったときに初めて読み込まれるものがあり、後者はどの触り方で読み込まれるかまで条件があります。①は、その条件が意図と食い違っていた話です。
「エラーが出ていないから、効いている」
飛ばされたときも、時間切れしたときも、照合が無視されたときも、エラーは出ません。終了コード1で失敗したときさえ進みます。静かなことは、効いている証拠ではありません。
「自作の安全装置を入れてあるから大丈夫」
私がこれでした。安全装置が検査コマンドの中にある限り、守れるのは「呼ばれたが中身が無い」場合だけです。「呼ばれなかった」場合は守れません。どちらの穴に備えた装置なのかを、分けて考える必要があります。
引用できる数字と条件のまとめ
- 修正が入った版は v2.1.288・2026年10月2日(公式変更履歴と GitHub リリースの2か所で逐語一致)
- ①の対象はパス指定の
.claude/rulesと入れ子の CLAUDE.md。修正前は Read だけが読み込んでいた - ②の対象は PreToolUse と PermissionRequest。飛ばされる条件は照合の失敗と入力の JSON 変換の失敗の2つのみ。修正後は呼び出しを止める
- 時間切れの既定は
command・http・mcp_toolが 600秒(ユーザー入力時とモデル切替前後は30秒、メッセージ表示時は10秒) - 時間切れしたフックは出力を破棄され、PreToolUse では呼び出しを止めない
- 止める終了コードは 2 だけ。1 は止めずに進む
- 照合に対応しないイベントの
matcherは黙って無視される load_reasonの値はsession_start/nested_traversal/path_glob_match/include/compactの5種- 私の稼働中の実測:CLAUDE.md 88本(入れ子87本・深さ2が45本)/ルール68本(
paths:持ちは0本)/検査7本(PreToolUse 3・PostToolUse 2・Stop 2) - 検査7本の時間切れ:2本が未指定(=600秒)、最長の指定は300秒
- 手元の版は 2.1.209=①②はいずれも未適用
まとめ
検査が効かないとき、設定の書き方を直す前に確かめることがあります。その検査は呼ばれたのか、という点です。
2026年10月2日の修正2件は、どちらも「呼ばれる/読み込まれる」側の不具合でした。パス指定のルールと入れ子の CLAUDE.md は書くときに読み込まれておらず、PreToolUse と PermissionRequest は照合に失敗すると飛ばされていた。どちらも、書いた内容が正しいかどうかとは無関係に起きます。
私は自分の環境を数えて、片方は無関係(ルールに paths: が0本)で、片方は直撃(入れ子の CLAUDE.md が87本)だと分かりました。数える前は、両方を直撃だと思っていました。自分の設定でさえ、数えないと影響範囲を間違えます。
そして確かめる手段はあります。InstructionsLoaded で load_reason を記録し、Write だけの操作で nested_traversal が出るかを見る。設定を読み返すのをやめて、記録を数える。私たちが無人のジョブに検査を仕掛けるときに、いちばん効いた切り替えはこれでした。
Claudeのこの5つの設定、今すぐ見直した方がいい

共有・学習・入力・権限・委任。事故が起きるのはこの5つだけ(全28ページ)
2026年7月、Claudeの共有チャットがGoogle検索から読める状態になっていました。原因は設定そのものではなく、自分が過去に共有したものを覚えていないことでした。共有リンクの棚卸しから、学習をオフにしても残る例外条項、Claude Codeの権限モードまで、今日30分で確認できる形にまとめています。
- 見落としやすい共有リストは3つある(3つ目は画面の下)
- 学習をオフにしても残る、公式の例外条項
- 権限モード6つの違いと、Shift+Tabでの切り替え方
- 私が実際に禁止しているコマンド23行を全文公開
メールアドレス登録で他にも様々な資料を閲覧できます








+8PDF 17点・合計440ページ + すぐ使えるzip素材 3点
どれも登録後の受け取りページから、まとめてダウンロードできます。
毎週配信の無料ニュースレター「AIネイティブ超研究」の購読特典です。メール登録後すぐ、受け取りページのご案内が届きます。そこにはこの資料に加えて、過去の特典もすべてまとめて置いてあります。あわせて、AI活用に関するお知らせやお役に立てそうなご案内をお送りすることがあります。解除はいつでも1クリック。
御社の業務に合わせたClaude Code導入支援
「AIツールを導入したが、現場で使われない」を終わらせる。
業務課題のヒアリングから設計、ハンズオン実践、運用定着まで一貫して支援します。