Claude Media
Claude Code Setup/InstructionsLoadedフックの実務設定

Claude Code Setup/InstructionsLoadedフックの実務設定

SetupフックはCI初期化専用で通常起動では発火しません。InstructionsLoadedはCLAUDE.mdの読み込みを監査するだけで止められません。実務での設定例とありがちな誤解をまとめます。

Setupはv2.1.10、InstructionsLoadedはv2.1.69で追加されたイベントで、性格がまったく違います。SetupはCIやスクリプトからの明示的な初期化専用で、普段のclaude起動では一度も発火しません。InstructionsLoadedはCLAUDE.mdの読み込みを止められない代わりに、どのファイルがいつ読み込まれたかを漏れなく記録できます。この2つを混同すると、「初期化フックを書いたのに動かない」「監査ログのつもりが実は処理を止めていた」という食い違いが起きます。

前提として、.claude/settings.jsonにhooksを書いた経験があることを想定します。フックの設定構文そのもの(matcher・hooks配列・イベント一覧の全体像)はClaude Code Hooks完全ガイドにまとまっています。本記事はSetupInstructionsLoadedという2つのイベントの実務的な使い方に絞ります。

両方とも、コマンドフックであればsession_idtranscript_pathcwdhook_event_nameという共通フィールドをJSONで標準入力から受け取ります。個別フィールド(SetuptriggerInstructionsLoadedfile_pathなど)はこれに追加される形で届きます。監査ログを組むときは、この共通フィールドのsession_idが「どのセッションでの出来事か」を後から突き合わせる鍵になります。

SetupとInstructionsLoadedは何が違うか

項目SetupInstructionsLoaded
いつ発火するかSetup--init-only、または-p--init/--maintenanceInstructionsLoadedセッション開始時の即時ロードと、後続のCLAUDE.md遅延ロード時
通常起動での発火SetupしないInstructionsLoadedする(読み込む指示ファイルがあればセッション開始時に発火)
ブロック・出力の反映Setupできない(出力は全終了コードで破棄)InstructionsLoadedできない(出力は常に破棄)
主な用途Setup依存関係のインストール、定期メンテナンスInstructionsLoaded監査ログ、コンプライアンス記録
近い既存イベントSetupSessionStart(毎回発火する初期化)InstructionsLoadedなし(専用の観測イベント)

どちらも決定制御(ブロックやコンテキスト注入)を持たない点は共通しています。ここがPreToolUseUserPromptSubmitのような判定系のフックと根本的に違います。判定系のフックは「通す・止める・書き換える」ための出口を持ちますが、SetupInstructionsLoadedにはその出口自体がありません。設計段階でこの制約を踏まえておかないと、あとから「ブロックしたい」という要件が出たときにイベントごと選び直す羽目になります。

ステップ1 — Setupフックでリポジトリ初期化を書く

Setupclaude --init-only、または非対話モードの-pフラグに--init--maintenanceを添えたときだけ発火します。matcherにはどちらのフラグが起点かが入ります。

{
  "hooks": {
    "Setup": [
      {
        "matcher": "init",
        "hooks": [
          {
            "type": "command",
            "command": "npm install && npm run build"
          }
        ]
      },
      {
        "matcher": "maintenance",
        "hooks": [
          {
            "type": "command",
            "command": "./scripts/cleanup-stale-branches.sh"
          }
        ]
      }
    ]
  }
}

matcher: "init"claude --init-onlyまたはclaude -p --initで発火し、依存関係のインストールのような一度きりの準備に向きます。matcher: "maintenance"claude -p --maintenance専用で、定期クリーンアップのようなスケジュール実行を想定しています。通常のセッション開始(claudeだけの起動や--resume)ではどちらも発火しません。

想定する運用は2つに分かれます。ひとつはCIパイプラインの先頭でclaude --init-onlyを1回実行し、以降のエージェント実行が依存関係の揃った状態から始まるようにする使い方です。もうひとつはcronやスケジューラからclaude -p --maintenanceを定期実行し、古いブランチの掃除やキャッシュの整理をClaude Codeの外側のジョブとして回す使い方です。どちらも「セッションが始まるたびに」ではなく「明示的に呼んだときだけ」動く前提で設計します。

ステップ2 — 実行結果を確認する

claude --init-onlyを実行すると、成功時はターミナルに何も表示されません。フックが実際に走ったかを確認するには--debug-fileでログファイルを指定します。

claude --debug-file /tmp/claude-init.log --init-only

ログにはSetupと、続けて発火するSessionStart(matcher: "startup")のフックエントリが記録されます。Setupは成功・失敗にかかわらず実行を継続し、systemMessagecontinueのようなJSON出力フィールドはすべて破棄されます。これはInstructionsLoadedとも共通する挙動で、どちらのイベントも「処理を止める・変える」ための出口を持ちません。-p実行時は--output-format stream-json --verboseを付けると、標準出力・標準エラー・終了コードがhook_responseイベントとして出力に現れます。

-pで会話を開始・継続する場合、プロンプトを引数か標準入力で渡す必要があります。ただしSessionStartフックがinitialUserMessageを渡す設計になっている場合や、deferされたツール呼び出しからセッションを再開する場合は省略できます。

ステップ3 — CLAUDE_ENV_FILEで環境変数を後続コマンドへ渡す

SetupフックはCLAUDE_ENV_FILEにアクセスできます。ここにexport文を書き込むと、そのセッションの以降のBashコマンドに環境変数が引き継がれます。

#!/bin/bash
if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
fi
exit 0

>>で追記するのは、SessionStartCwdChangedなど他のフックが設定した変数を上書きしないためです。CLAUDE_ENV_FILEを持つイベントはSessionStartSetupCwdChangedFileChangedの4つに限られ、それ以外のイベントにこの変数はありません。

Setupは毎回の起動では走らないため、プラグインが依存パッケージのインストールをSetupだけに頼るのは危険です。実務的なパターンは、初回利用時に依存の有無を確認し、無ければその場でインストールすることです。たとえば${CLAUDE_PLUGIN_DATA}/node_modulesの存在をフックやSkillでテストし、無ければnpm installを走らせます。マーケットプレイス配布のプラグインであれば、Claude Codeがキャッシュ時にNode.jsパッケージ依存を自動インストールするため、この保険自体が不要になるケースもあります。

InstructionsLoadedで指示ロードを監査する

InstructionsLoadedはCLAUDE.mdや.claude/rules/*.mdが読み込まれるたびに発火します。セッション開始時の即時ロード分と、ネストしたディレクトリへのアクセスやpaths:frontmatterの条件一致による遅延ロード分の、両方が対象です。

入力フィールド内容
file_path内容読み込まれた指示ファイルの絶対パス
memory_type内容User / Project / Local / Managedのいずれか
load_reason内容session_start / nested_traversal / path_glob_match / include / compact
globs内容path_glob_matchのときだけ、paths:に書かれたパターン
trigger_file_path内容遅延ロードのきっかけになったファイル
parent_file_path内容includeロードでの親ファイル

matcherload_reasonに対して評価されます。セッション開始時のロードだけを拾いたいなら"matcher": "session_start"、遅延ロードだけなら"matcher": "path_glob_match|nested_traversal"のように絞り込みます。

{
  "hooks": {
    "InstructionsLoaded": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c '{ts: now, session: .session_id, file: .file_path, scope: .memory_type, reason: .load_reason}' >> ~/instructions-audit.log"
          }
        ]
      }
    ]
  }
}

このコマンドは標準入力のJSONをjqでそのまま整形し、いつ・どのセッションで・どのファイルが・どのmemory_type(User / Project / Local / Managed)で・どんな理由で読み込まれたかを1行ずつ追記します。組織で強制配布しているManagedスコープのCLAUDE.mdが実際に各セッションへ効いているかを確認したいときは、scopeManagedの行だけを後から絞り込めば済みます。

大規模なモノレポでCLAUDE.mdを階層化している場合、InstructionsLoadedは「どのセッションでどのCLAUDE.mdが実際に効いたか」を後から追跡する手段になります。階層化そのものの設計はClaude Codeモノレポ設計で扱っています。ネストしたディレクトリへ移動したときのnested_traversalpaths:条件に一致したときのpath_glob_match、コンパクション後の再読み込みを示すcompactまで、どの経路で読み込まれたかをload_reasonで区別できるため、「意図した粒度でルールが分割できているか」を運用しながら検証できます。

監査ログとしての性格上、InstructionsLoadedはロードを拒否したり内容を書き換えたりはできません。読み込みそのものを制御したい場合は、paths:frontmatterによる条件分岐や、ファイルの配置構成そのものを見直す必要があります。

よくあるつまずき

  • SessionStartと同じ感覚で書くと、Setupが通常起動で発火しないことに気づかず、claudeと打つだけの日常利用では一度も実行されません。プラグインの初期セットアップをこれだけに頼ると、多くのユーザーがCIを経由せずにclaudeを直接起動するため、フックが一度も走らないまま使われ続けることになります。CI・スクリプト経由の--init-only/--init/--maintenanceでしか動かない前提で設計します
  • SetupInstructionsLoadedも、systemMessageadditionalContextを含むJSON出力フィールドはすべて破棄されるため、出力で処理を制御しようとしても効きません。何かを分岐させたい・コンテキストに情報を足したいといった要件が出てきたら、そもそもイベント選定が間違っている合図です。副作用(ファイル書き込みやログ追記)で完結させる設計にします
  • --init-onlyは成功時にターミナルへ何も表示しないのが仕様で、無反応に見えるとフックが壊れているのか正常終了なのか見た目では区別できません。動作確認には--debug-fileでログを見るか、明示的にechoなどの副作用をフック側に仕込みます
  • InstructionsLoadedのmatcherが評価するのはload_reasonであってfile_pathではないため、ファイル名を絞ろうとしても動きません。特定のCLAUDE.mdだけを対象にしたい場合は、matcherを*のままにして、ハンドラースクリプト側でfile_pathを見て分岐します
  • SessionStartinitialUserMessageを供給しない限り、-pは引数か標準入力でプロンプトを要求するため、-p --initプロンプトを渡し忘れるとハングします。CIのジョブ定義でこれを見落とすと、パイプラインがプロンプト待ちのまま止まります

よくある質問

Setupフックはsettings.jsonのどこに書けますか

書ける場所は他のフックと同じです。ユーザー設定・プロジェクト設定・ローカル設定・managed settings・プラグインのhooks/hooks.json・Skillやサブエージェントのfrontmatterのいずれにも書けます。複数の場所に定義すると、上書きではなく合算されます。プロジェクト側で追加したSetupフックが、managed settings側のSetupフックを消すことはありません。

InstructionsLoadedはコンパクション後の再読み込みも拾いますか

拾います。load_reasoncompactになるのがこのケースで、会話が圧縮されたあとに指示ファイルが再読み込みされたことを示します。セッション開始時のsession_startや、ディレクトリ移動時のnested_traversalと同列の理由コードとして扱われます。

まとめ

SetupはCI・スクリプトから明示的に起動したときだけ動く初期化フックで、依存インストールや定期メンテナンスに向きます。InstructionsLoadedはCLAUDE.mdの読み込みを止められない代わりに、セッション開始時と遅延ロードの両方をload_reasonつきで観測できる監査用のイベントです。どちらも決定制御を持たないため、「止める・変える」処理はPreToolUseUserPromptSubmitのような別のイベントに任せ、この2つは副作用の記録に徹するのが実務上の使い分けです。

導入する順番としては、まずInstructionsLoadedで現状のCLAUDE.md読み込みパターンをログに残し、想定どおりのmemory_typeload_reasonで読み込まれているかを確認してから、Setupで初期化スクリプトを整えるのが手戻りの少ない進め方です。読み込みの実態を見ないまま初期化だけ自動化すると、想定と違うスコープのCLAUDE.mdが効いていたことに後から気づく、という順序の逆転が起きがちです。hooksを初めて設定する場合はClaude Code Hooksの設定方法から、リリース自体の背景はClaude Code v2.1.10から確認できます。

この記事を共有:XはてブLinkedIn