Hookはすでに設定済みで、/hooksコマンドにも表示されています。それなのに、何かがおかしい。リクエストを送っても、requests.logに新しい行が現れません。あるいは2回現れます。もっと厄介なのは、どんな修正のあとにも自動処理が走り出して、普段の作業の邪魔になるケースです。

こういうときに設定をゼロから組み直す必要はありません。観察したことを、確認できる小さな課題に変えるほうが役に立ちます。何がhookを呼ぶべきか、どんな痕跡が残るべきか、どのケースを通過させてはいけないか。そうすればClaude Codeは該当するファイルを確認し、最小限の修正を提案できます。

hookはいつ発火すべきか

この記事では、設定ファイルに書かれたコマンド型hookを取り上げます。このタイプのhookには3つの要素があります。イベント、実行条件、そしてハンドラーです。イベントは"いつ見るか"という問いに答え、条件は該当するケースを絞り込み、ハンドラーが処理を実行します。

たとえば、リクエストログはUserPromptSubmitを購読しています。このイベントは、セッションにリクエストが届いた時点、モデルがそれを処理する前に発生します。たいていはあなたのリクエストですが、Claude Codeは自分から始めるターンでもこのイベントを発生させます。/loopの次の周回や、バックグラウンドのサブエージェントの報告などです。つまりログの新しい行が裏付けるのはごくわずかなことだけです。hookがリクエストを受け取り、それを記録した、それだけです。エージェントがタスクを正しくこなしたかどうかは、この行からは分かりません。

UserPromptSubmitにはmatcherがありません。ログに特定のリクエストだけが必要な場合、その選別はハンドラー自身が行います。ツール系のイベントは事情が異なります。たとえばmatcherのEdit|Writeはツールを選びますが、ファイルパスは呼び出しデータから別途確認します。ツール名のmatcherは"src配下のファイルだけ"を意味しません。

イベントと結果を混同しない

Stopイベントは、Claudeが応答を終えたことを意味します。通知を出すには便利なタイミングですが、コードが正しいこと、タスクが無事に完了したことの証明にはなりません。

修正の前に、短いシナリオを2つ書き出しておきましょう。

  • 期待するケース: テストリクエストを送り、requests.logに新しい行が1つ現れること。
  • 通過してはいけないケース: 通常のエディタでファイルを開き、Claude Codeへの新しいリクエストを送らずに、ログが変わらないことを確認する。すべてのリクエストを記録するログなら、2回目に送ったpromptにも記録が現れるはずなので、それはネガティブケースの確認にはなりません。

これは確認の基準です。Claude Codeの具体的な回答をあらかじめ想定しておく必要はありません。

記録が現れない

観察結果が期待とずれている場所から始めましょう。ログの場合、それはテストリクエストのあとに新しい行が現れないことです。そこからは、設定から実際の動作へと順に見ていきます。

  1. Claude Codeの対話セッションで/hooksを入力し、必要な設定が読み込まれているか確認する。このメニューはあなたが開くもので、確認結果はエージェントに渡します。
  2. イベントを確認する。リクエストログはUserPromptSubmitに反応するべきで、ファイル変更後の処理は普通ツール系イベントに紐づけます。
  3. 設定の元ファイルを探す。ユーザー設定、プロジェクト設定、ローカル設定のいずれかの可能性があります。名前だけでなくコマンド全体を比較すること。同じハンドラーが複数の設定ファイルに見つかっただけでは、2回実行される根拠にはなりません。
  4. ハンドラーのコマンドを確認する。ファイルが存在するか、コマンドがOSに合っているか、スペースを含むパスが壊れていないか。
  5. 結果の出力先を確認する。相対パスはプロセスの作業ディレクトリに依存するため、あなたが探しているのとは別の場所に、ハンドラーがファイルを書き込んでいる可能性があります。

ここでの主な進め方はエージェントを通すことです。観察結果、/hooksの確認結果、修正の範囲を伝えましょう。

UserPromptSubmitのhookは/hooksに表示されているが、テストリクエストのあとも
requests.logに新しい行がない。

元の設定とハンドラーを見つけて。イベント、コマンドのパス、
作業ディレクトリ、ログ用ディレクトリの作成を確認して。原因を示し、
最小限の修正を加えて。ほかのhookと設定はそのまま残して。
修正後、期待するケースとネガティブケースの確認シナリオを説明して。
修正箇所を確認する

エージェントに、変更するファイルと正確な行を名指ししてもらいましょう。そうすれば、元の設定を直しているのか、それとも読み込まれてすらいない、たまたま見つかったコピーを直しているのかが分かります。

たとえば、エージェントが次のことを突き止めたとします。ハンドラーはlogs/requests.logに書き込むが、logs/フォルダが存在せず、スクリプトもそれを作らない。最小限の修正は、書き込み前にこのフォルダを作成することです。修正後はlogs/requests.logそのものを確認してください。テストリクエスト1回につき記録が1件増えるはずです。これはあくまで考えられる原因の一例であり、空のログすべてに当てはまる診断ではありません。

設定はファイルにあるのに/hooksに出てこない場合、原因はJSONの構文、設定のスコープにあるかもしれません。あるいは、そもそも今hooksが実行されない状態なのかもしれません。プロジェクトのフォルダーが信頼済みになっていない、disableAllHooksが有効になっている、管理者が管理ポリシーのhooksしか許可していない、といった場合です。異なる設定ファイルのhooksは互いに置き換わるのではなく、合算されます。設定が読み込まれているのに痕跡がない場合は、コマンド、入力データ、出力先のパスに注目が移ります。

/hooksメニューの確認結果によるhook診断の分岐リクエスト後に新しい行がない記録は/hooksに見えるか?イベント、ソース、コマンドを確認設定側の問題を探すJSON、スコープ、フォルダーの信頼ハンドラーを確認するコマンド、stdin、出力先のパスいいえはい
/hooksでの確認結果によって、次にどの層を調べるかが決まる

この分岐の意味はシンプルです。Claude Codeが記録を読み込むまでは、スクリプトの動作を調べるのはまだ早い。記録が見えている段階では、設定はすでにアプリケーションまで届いているので、ハンドラーの実行、入力のJSON、結果が現れるべき場所を確認するほうが有益です。

ハンドラーがエラーで終わる

コマンド型hookは標準入力経由でJSONを受け取ります。そこにはイベント共通のフィールドと、そのイベント固有のデータが入っています。UserPromptSubmitの場合、リクエストのテキストはpromptフィールドに入ります。このテキストはデータとして扱う必要があります。shellコマンドに連結して実行してはいけません。テストリクエストには引用符、改行、あらゆる通常のテキストが含まれる可能性があります。

Claude Codeに、架空の入力でハンドラーの実行を再現してもらいましょう。リクエストには重要なケースをあらかじめ書いておきます。

セッションとは別に、架空のJSONに対してhookのハンドラーを確認して。
必要なケース: 通常のpromptと複数行のprompt、スペースを含むプロジェクトパス、
ログ用ディレクトリが存在しないケース、不正な入力。

promptのテキストをコードとして実行しないで。実行コマンド、終了コード、
stderr、テストファイルの実際の変更を示して。本番のログは変更しないで。

終了コードはハンドラー自体がどうなったかを知るのに役立ちますが、その意味はイベントによって異なります。exit 0は、hookコマンドがエラーなく終了したことを示します。これはタスク全体の結果を保証するものではありません。exit 2は"前"に発生するイベントでは処理をブロックできますが、ツールがすでに実行されたあとでは、その動作を取り消すことはできません。だから、あるイベントで使えたexit 2のやり方を、ほかのイベントにそのまま流用してはいけません。

単独の実行は成功しているのに、セッション内ではhookがまだ沈黙しているなら、プロジェクトフォルダからclaude --debug-file hook-debug.logコマンドでClaude Codeを起動し、テストリクエストをもう一度送ってみましょう。現れたhook-debug.logを開き、イベント、一致したhooks、終了コードを探します。これはあなたのrequests.logとは別の、Claude Code自身のログです。確認が終わったらこの診断用セッションは終了し、通常の作業ではフラグなしでClaude Codeを起動してください。ほとんどのイベントの通常のstdoutはdebug logに送られ、transcriptに必ず現れるとは限りません。何が例外になるかはイベントによって異なるため、stdoutに任意の診断用の行を出力すると、便利なログになるどころか、コンテキストに影響してしまう可能性があります。

診断は結果と分けて行う

最初の切り分けでは、診断用の記録は別のローカルファイルに書き、確認後に削除するほうが安全です。公式ドキュメントにはhooks向けの汎用的なdry-runフラグはありません。試行モードは、それぞれの学習用ハンドラーの実装として個別に用意します。

hookが余分に発火する

1回のリクエストのあとに同じ行が2つ現れる場合、重複登録が原因のことがあります。たとえば、似ているものの完全には一致しないhooksが、ユーザー設定とプロジェクト設定の両方に置かれているケースです。もし文字どおり同じコマンドが複数のファイルに繰り返し書かれているだけなら、実行は1回で済みます。pluginやskill由来のコピーは別枠でカウントされるため、メニュー上の出どころも重要です。まずエージェントに/hooksの結果を渡し、そのイベントの登録を利用可能なすべての設定ファイルで列挙してもらいましょう。設定側に問題がなければ、次はハンドラー自体が1回のリクエストで2回書き込んでいないか確認します。最初に見つかった行をすぐ削除するのは早計です。

もう1つよくある原因は、条件が広すぎることです。たとえば、hookはsrc/配下のファイルが変更されたときだけ診断用の記録を残すべきなのに、EditとWriteのあらゆる使用に反応してしまうケースです。ここには2段階の絞り込みが必要です。

  • matcherがEditとWriteのツールを選ぶ。
  • 別の条件がツール呼び出しの引数からパスを確認し、src/以外はすべて除外する。
PostToolUse hookの2段階の絞り込みPostToolUseイベントツールはEditかWriteか?hookをスキップパスはsrc/内か?呼び出し引数を確認記録を残さない記録を書き込むいいえはいいいえはい
matcherがツールを選び、別の条件がファイルの範囲を絞り込む

片方の確認はもう片方の代わりにはなりません。matcherが不適切なツールをふるい落とし、そのあと引数による条件がsrc/配下のパスだけを残します。診断用の記録が現れるのは、両方の確認が"はい"を返したときだけです。

設定の断片はこのような形になります。

"PostToolUse": [
  {
    "matcher": "Edit|Write",
    "hooks": [
      { "type": "command", "command": "python .claude/hooks/mark-source-edit.py" }
    ]
  }
]

これは既存のsettings.jsonにあるhooksオブジェクトの一要素であり、完全なJSONドキュメントではありません。パスの確認はハンドラー側で行う必要があります。mark-source-edit.pyというスクリプトはここでは仮のもので、その中身はこの記事にはありません。このコマンドはpythonが使えること、プロジェクトのルートから実行することを前提としています。インタプリタ、シェル、パスはご自身の設定に合わせてエージェントに確認してもらってください。この例を、そのまま使える設定としてコピーしないでください。

この断片は、ひとつながりの文として読むと分かりやすくなります。

  • ファイルの修正が成功したあと
  • ツールがEditまたはWriteなら
  • mark-source-edit.pyを実行する

この連なりが示しているのは、設定に書かれていることだけです。"記録を残すか、それともファイルをスキップするか"を決めるのは、パスを確認したあとのハンドラーです。したがってmatcherの存在は、ツールが選ばれたことは裏付けますが、src/の絞り込みが正しく行われていることまでは証明しません。

操作期待される痕跡何を確認しているか
Editでsrc/card.jsを変更新しい診断用記録が1件正しいツールと正しいパスが条件を通過する
Writeでsrc/theme.cssを作成新しい診断用記録が1件2つ目の許可された操作も反映される
EditでREADME.mdを変更新しい記録なしsrc/外のファイルは除外される

この表でのEditとWriteは、Claude Codeのツールを指します。エディタで手動でファイルを変更してもこれらは呼ばれないため、このmatcherの確認にもなりません。各操作の前に診断用ファイルの末尾を控えておき、あとで新しく増えた記録だけを比較してください。

絞り込みの境界が確認できていないうちは、動作は無害なものにとどめましょう。フォーマットやブロック、ファイルの一括修正はせず、発火を示す記録を1件残すだけにします。まずは期待するケースがちょうど1回だけ通過し、余分なケースは通過しないことを確かめてください。

余分な発火は必ず余計な作業を生みます。一方で、ファイルに書き込むだけのローカルコマンドは、それ自体ではモデルにアクセスしません。prompt型やagent型のhookは仕組みが異なり、モデルを使います。コストの詳しい話はここでは不要で、この2種類のハンドラーを混同しないことだけ押さえておけば十分です。

反応を止める方法

hookが今まさに邪魔をしているなら、その元の登録を見つけて、該当イベントの配列からそれだけを削除してください。ほかのhooksはそのままにします。これは今後の実行を止める操作です。すでに実行されたコマンドを取り消すわけではなく、変更されたファイルを元に戻すわけでもなく、すでにログに入ってしまった行を消すわけでもありません。

後で戻すつもりなら、削除する前に選んだ登録のコピーを保存しておきましょう。変更後は/hooksをもう一度開き、その登録がなくなっていることを確認します。メニューがまだ古い設定を表示しているなら、セッションを終了し、同じプロジェクトから新しく開いてください。そのあと確認用の操作を実行し、前後でログを比較します。無効化したhookからの新しい行は現れないはずです。

すべてのhooksを一時的に無効化する"disableAllHooks": trueという設定もあります。ただしこれはすべてをまとめて止めるスイッチであって、1件だけを無効化する方法ではありません。この設定自体には設定ファイルの優先順位が効きます。プロジェクトのファイルにある"disableAllHooks": falseは、あなたのユーザー設定のtrueを上書きします。さらに、組織の管理ポリシーで指定されたhooksは、通常のユーザー設定やプロジェクト設定では無効化できません。

無効化と変更の取り消しを混同しない

無効化したhookは今後実行されなくなります。ハンドラーがすでにファイルを変更していた場合、そのファイルの復元は別の作業であり、diffや保存済みのバージョンを確認したうえで行ってください。

修正をClaude Codeに任せる方法

良い依頼には、観察結果、期待するイベント、具体的なファイル、修正の範囲が含まれています。これだけあれば、エージェントは自動化全体を書き直すことなく設定を調査できます。

このプロジェクトのhookを調べて。

観察結果: テストリクエストを1回送るとrequests.logに2行増える。
このセッションでは/loopもバックグラウンドのサブエージェントも動かしていない。
期待値: UserPromptSubmitはちょうど1行だけ追加するべき。
/hooksの結果を添付する。利用可能なすべての設定ソースと
ハンドラーのコマンドを確認して。

まず原因と、変更が必要なファイルを教えて。そのうえで
最小限の修正を加え、ほかのhooksはそのまま残して、diffを見せて。
期待するケースとネガティブケースを確認して。そのあと、
この反応だけを無効化する方法を教えて。

最終的に、はっきりした答えが4つ手元に残るはずです。

  • どのイベントがhookを起動するか。
  • どの条件が余分なケースを通過させないか。
  • 元の設定とハンドラーはどこにあるか。
  • ほかに影響を与えずに、この反応だけをどう無効化するか。

そして確認の流れ全体はこうなります。期待する操作は1つの想定どおりの痕跡を残し、余分な操作は痕跡を残さず、ハンドラーのエラーはピンポイントの診断で見え、無効化後は新しい痕跡が現れない。かなり地に足のついた話です。だからこそ、こういうhookは"たまに何かをする"だけの自動化より保守しやすいのです。

動作を確認する際は、公式のhooksリファレンスと実践ガイドを使ってください。インターフェースはClaude Codeとともに変わります。

動作は2026年9月18日時点の公式ドキュメントで確認しています。例は確認の手順を示すものであり、そのまま使えるハンドラーではありません。コマンドとパスはご自身のOSとシェルで確認してください。