Claude Code のセッションでは 30 個の hook イベントが発火する。応答を返せるのは 3 つだけ。
Claude Code の hook イベントの完全な一覧、それぞれがいつ発火するのか、どの 15 個がブロックできるのか、そしてほとんどの hook 出力を黙って飲み込む stdout のルール。数千のエージェントセッションで hook を本番運用して作り上げた実地リファレンス。
Claude Code に hook を組み込むとき、2 つの失敗が何度も繰り返し現れます。そして両者はまったく似ていません。
1 つ目:hook を追加しても、何も起きない。エラーも警告もログ行もない。hook が単に一度も実行されない。
2 つ目:hook は明らかに実行されていて、ディスク上の副作用も見えるのに、エージェントに向けて出力したメッセージが決して届かない。エージェントは、hook が何も言わなかったかのように振る舞う。
どちらも出どころは同じです。hook のシステムは、ほとんどの記事が扱う一握りのイベントよりも大きく、そして一様ではありません。しかも誰がエージェントに話しかけられるかを決めるルールは、直感で当てられるものではありません。これは、私たちが最初から欲しかったリファレンスです。私たちは AgentsRoom をこれらの hook の上に構築しており、以下の内容はすべて、公式リファレンスからの引用か、本番環境で実測したものです。
イベントは 30 個であって、6 個ではない
ほとんどのガイドが扱うのは PreToolUse、PostToolUse、UserPromptSubmit、Stop、Notification、SubagentStop です。この 6 つは実在し、有用な仕事の大半を担っています。同時に、存在するもの全体の 5 分の 1 でもあります。
完全な一覧を、それぞれが何を観測するかでグループ分けすると次のようになります。
| グループ | イベント |
|---|---|
| セッション | SessionStart、SessionEnd、Setup |
| プロンプト | UserPromptSubmit、UserPromptExpansion |
| ツール | PreToolUse、PostToolUse、PostToolUseFailure、PostToolBatch |
| パーミッション | PermissionRequest、PermissionDenied |
| ターン | Stop、StopFailure |
| サブエージェントとタスク | SubagentStart、SubagentStop、TaskCreated、TaskCompleted、TeammateIdle |
| コンテキスト | PreCompact、PostCompact、InstructionsLoaded |
| 環境 | FileChanged、CwdChanged、ConfigChange |
| Worktrees | WorktreeCreate、WorktreeRemove |
| インターフェース | Notification、MessageDisplay |
| MCP elicitation | Elicitation、ElicitationResult |

1 つのセッションでイベントが発火する順序。ツールのブロックはツール呼び出しごとに繰り返され、プロンプトのブロック全体はターンごとに繰り返されます。
このうちいくつかは、システムの捉え方そのものを変えます。PostToolUseFailure が存在するということは、「ツールは動いたのか」という分岐がイベントであって、ペイロードから推測するものではないということです。PostToolBatch は並列のツール呼び出しのバッチが解決したあとに 1 回だけ発火するので、編集ごとに 1 回ではなく、linter を 1 回だけ走らせるのに適した場所です。InstructionsLoaded は CLAUDE.md が読み込まれたときに発火するので、あなたが読み込まれていると思っているルールをエージェントが本当に読み込んだのかどうかを確認する hook ポイントが手に入ります。
ほとんどの hook 出力を飲み込む stdout のルール
このページで最も役に立つのは、この一点です。
終了コード 0 のとき、Claude Code は stdout を解析して JSON の出力フィールドを探します。しかし、その stdout がエージェントに見せられるかどうかはイベント次第で、例外は短いリストに収まります。リファレンスにはこうあります。
ほとんどのイベントでは、stdout は debug ログに書き出されますが、トランスクリプトには表示されません。例外は
UserPromptSubmit、UserPromptExpansion、SessionStartで、これらでは stdout が、Claude が見て行動に移せるコンテキストとして追加されます。
30 個のうち 3 個です。PostToolUse の hook から echo "warning: this migration is destructive" を実行し、エージェントがそれを読んでくれると期待しても、決して読まれません。あなたのテキストは debug ログに行きました。
ほかのイベントからエージェントの目の前にテキストを届ける方法は、ちょうど 2 つあります。
- 終了コード 2 で stderr に書き出す。 終了コード 2 のとき、Claude Code は stdout とその中の JSON をすべて無視し、stderr をエラーメッセージとしてエージェントに返します。
- 終了コード 0 で JSON オブジェクトを出力する。 そこに
hookSpecificOutput.additionalContextを載せます。
1 つ目の非対称性に注目してください。終了コード 0 では stdout が意味を持ち、stderr は持ちません。終了コード 2 では stderr が意味を持ち、stdout は完全に捨てられます。これを逆に理解していることが、hook が完全に正しく見えるのに黙ったままになる理由です。
それ以外の終了コードは、ブロックしないエラーです。トランスクリプトには stderr の 1 行目を添えた <hook name> hook error の通知が表示され、実行は継続し、stderr の全文は debug ログに落ちます。

終了コードごとに、どのチャネルがエージェントに届くか。破線の経路は、あると思われがちで実際には存在しないものです。
ちょうど半分がブロックできる
15 個のイベントは終了コード 2 でアクションを止めます。15 個はそれを無視して進みます。
ブロックできる: PreToolUse、PermissionRequest、UserPromptSubmit、UserPromptExpansion、Stop、SubagentStop、TeammateIdle、TaskCreated、TaskCompleted、ConfigChange、PostToolBatch、PreCompact、Elicitation、ElicitationResult、WorktreeCreate。
ブロックできない: PostToolUse、PostToolUseFailure、PermissionDenied、StopFailure、Notification、SubagentStart、SessionStart、Setup、SessionEnd、CwdChanged、FileChanged、PostCompact、WorktreeRemove、InstructionsLoaded、MessageDisplay。
実務上の帰結:ガードレールは PreToolUse に置くものであって、PostToolUse には決して置きません。PostToolUse はツールが成功したあとに発火します。そこで終了コード 2 を返しても書き込みは取り消されず、被害がディスク上に残ったままエラーを表示するだけです。rm -rf を止めたいなら、それができる場所はちょうど 1 か所です。
PostToolUse はブロックしないのに PostToolBatch はブロックする、という点はもう一度見ておく価値があります。これは、並列の編集が着地したあとでもバッチレベルのチェックがターンを止められるということであり、このシステムが提供するもののなかで、書き込み後の拒否権に最も近いものです。
matcher は完全一致、そして突然そうでなくなる
matcher フィールドは、自分自身が含む文字によって評価戦略を切り替えますが、どちらの経路に入ったのかは何も教えてくれません。
| matcher | 評価のされ方 |
|---|---|
"*"、""、または省略 | すべてに一致 |
英字、数字、_、-、スペース、,、| のみ | 完全一致の文字列、または | か , で分割された完全一致の文字列のリスト |
| それ以外 | アンカーなしの JavaScript 正規表現 |
罠になるのは、アンカーなしという点です。リファレンスは、正規表現が RegExp.prototype.test でテストされると明示しています。これは値のどこかに一致すれば成功します。したがって Edit.* は Edit と NotebookEdit の両方に一致します。1 つのツールだけを指したかったのなら、^Edit$ と書いてください。
見当違いのデバッグを始める前に知っておきたい、バージョン依存の挙動が 2 つあります。
- カンマ区切りと空白の許容には、Claude Code v2.1.191 以降が必要です。
- ハイフンが完全一致の文字集合に加わったのは v2.1.195 です。それ以前は、
code-reviewerのような matcher はアンカーなしの正規表現として扱われ、senior-code-reviewerに対しても発火していました。
AgentsRoom では、自分たちのファイル帰属 hook を Write|Edit|MultiEdit|NotebookEdit で絞っています。これは完全一致の文字列の経路に留まり、この 4 つのツールだけに一致してほかには一致しません。私たちがインストールするライフサイクルの hook には matcher が一切ありません。常に私たちに関係するイベントだからです。
hook を定義できる場所は 6 つあり、それらはマージされる
本能的には優先順位を探したくなります。優先順位は存在せず、そこが面白いところです。
| 場所 | スコープ |
|---|---|
~/.claude/settings.json | あなたのすべてのプロジェクト、マシンローカル |
.claude/settings.json | 1 つのプロジェクト、コミット可能 |
.claude/settings.local.json | 1 つのプロジェクト、Claude Code が gitignore する |
| Managed policy settings | 組織全体、管理者が制御 |
プラグインの hooks/hooks.json | プラグインが有効な間 |
| skill または agent の frontmatter | そのコンポーネントが有効な間 |
リファレンスにはこうあります。
hook のエントリは、settings のレベルをまたいで互いを置き換えるのではなくマージされます。ユーザー、プロジェクト、ローカルの settings は、管理された hook を取り除くことなく自分自身の hook を追加し、
disableAllHooksの設定は managed settings の外から managed hook を無効化できません。
つまり、プロジェクトの hook がグローバルな hook を上書きすることは決してなく、その上に積み重なります。6 つのソースは、すべて加算的です。ユーザー settings で定義した PostToolUse のフォーマッタをプロジェクトでもう一度定義すると、編集ごとに 2 回実行され、唯一の症状は「なんとなく遅い」という感覚だけです。

6 つのソース、1 つのマージされたセット。ここでは何も、ほかの何かを上書きしません。
これは、あるツールが誰かのプロジェクトに hook をインストールする場所として .claude/settings.local.json が正しい理由も説明します。プロジェクト単位のスコープで、Claude Code がこれを gitignore し、CLI のフラグなしで読み込まれます。AgentsRoom がエントリを書き込むのはそこです。そのおかげで、ユーザーがコミットしている .claude/settings.json は決して触られず、同僚がマシン固有のパスを引き継ぐこともありません。
本番で hook を動かして学んだこと
AgentsRoom は、開いたすべてのプロジェクトに hook をインストールします。エージェントの状態を決定論的に追跡し、編集されたファイルを正しいエージェントに帰属させるためです。その規模になって初めて見えてくることがいくつかあります。
未知のイベント名は黙って無視される。 これはドキュメントに書かれていませんが、私たちはこれに依存しています。インストーラに新しいライフサイクルイベントを追加すると、古い CLI を使っているユーザーの手元には、そのバイナリが一度も聞いたことのないイベント名を含む settings.local.json が置かれます。何も壊れず、何も警告されず、そのエントリはスキップされます。CLI のリリースに先んじてインストーラを出荷しても安全なのは、これがあるからです。そして必然的に、つづり間違いがエラーではなく完全な沈黙を生むのも、これが理由です。
agent_id は、自分がサブエージェントの中にいることを知る手段。 このフィールドは、hook がサブエージェントの呼び出しの中で発火したときにだけ存在します。これは聞こえる以上に重要です。Stop は、メインエージェントだけでなく、サブエージェントがターンを終えたときにも発火します。「Stop が来たらセッションを完了とマークする」という素朴なルールは、どれか 1 つのサブエージェントが最初に戻ってきた時点で、セッション全体を完了にしてしまいます。私たちが agent_id を持つターン系イベントをスキップしているのは、まさにこの理由からです。
現在のターンについて transcript_path を読まないこと。 リファレンスは、トランスクリプトが非同期に書き出されるためメモリ上の会話に遅れることがあり、hook が発火した時点では最新のメッセージがまだそこにない可能性がある、と警告しています。Stop と SubagentStop が last_assistant_message を受け取るのは、ファイルとの競争をしなくて済むようにするためです。
hook は唯一の信頼できる状態シグナル。 hook 以前、私たちはエージェントが考えているのか、待っているのか、終わったのかを割り出すために PTY をスクレイピングしていました。それは、CLI が端末の代替スクリーンバッファ経由で描画した瞬間に壊れます。/tui fullscreen がやっているのが、まさにそれです。hook はどのレンダラーの下でも同じように発火します。外側からエージェントを観測する何かを作っているなら、これこそ土台にすべきレイヤーであり、スクレイピングはよくてもフォールバックどまりです。
async: true はコストゼロ。 hook のコマンドは async: true を宣言でき、エージェントはそれを待ちません。私たちの hook は 2 秒の上限を付けてローカルのエンドポイントに POST し、すぐに戻ります。受け取り側のアプリが閉じているときでも、エージェントのターンのレイテンシは影響を受けません。hook が観測するだけで何も決定しないなら、async にして、そのコストを払うのはやめましょう。
hook に端末へゴミを書かせないこと。 私たちのスクリプトは、トップレベルも含めてあらゆる例外を飲み込みます。hook から出た未処理のトレースバックは、静かに失敗するだけでは済みません。ユーザーが作業している最中の端末セッションに、Python のスタックトレースを印字します。
タイムアウト
デフォルトは寛大ですが、そうでない例外が 3 つあります。
| hook の種類 | デフォルトのタイムアウト |
|---|---|
command、http、mcp_tool | 600 秒 |
prompt | 30 秒 |
agent | 60 秒 |
UserPromptSubmit(command, http, mcp_tool) | 30 秒 |
MessageDisplay(command, http, mcp_tool) | 10 秒 |
SessionEnd | すべての hook で共有される 1.5 秒。hook ごとの timeout がより長ければ、それに合わせて最大 60 秒まで引き上げられる |
驚かれるのは SessionEnd の予算です。これは hook ごとの割り当てではなく共有の予算なので、明示的に引き上げない限り、3 つのクリーンアップ hook は 1.5 秒を分け合うことになります。
短くまとめると
- イベントは 30 個ある。有名なのは 6 個。
- stdout がエージェントに届くのは
UserPromptSubmit、UserPromptExpansion、SessionStartだけ。それ以外の場所では、終了コード 2 と stderr を使うか、JSON のadditionalContextを使う。 - 15 個のイベントは終了コード 2 でブロックし、15 個はそれを無視する。ガードレールは
PreToolUseに置く。 - matcher は完全一致の文字列で、特殊文字が 1 つ入った時点でアンカーなしの正規表現に変わる。
- 6 つの settings のソースは加算的にマージされる。何も、ほかの何かを上書きしない。
- イベント名のつづり間違いは、完全な沈黙のなかで失敗する。
これらのイベントについて頭で推論するのではなく、実際に発火するところを見たいなら、私たちが作ったのはまさにそれです。AgentsRoom は、数十の並列エージェントやサブエージェントのセッションにまたがって、エージェントごと、プロジェクトごと、実行ごとに、すべての hook のトリガーを表示します。あなたが自分の settings で設定した hook は、書いたとおりにそのまま動き続けます。AgentsRoom が本物の CLI を実行しているからです。
続きを読む
AgentsRoom が Kimi Code に対応
Moonshot AI のターミナル型コーディングエージェント Kimi Code が、AgentsRoom の正式なプロバイダーになりました。Claude、Codex、Antigravity CLI、OpenCode、Aider、Grok Build、Mistral Vibe と並べて動かし、会話の途中で切り替えられます。
記事を読む2026年のClaude AIエージェントのための最高のメモリソリューション
CLAUDE.md, mem0, Letta, Zep, basic-memory, ObsidianとMCP, AgentsRoomプロジェクトメモリ: AIコーディングエージェントのためのメモリオプションの正直な地図と、どのメモリを選ぶべきか。
記事を読む開発チーム全体でAIコーディングエージェントをスケールする方法
コーディングエージェントを持つ開発者1人は生産性の物語です。20のエージェントを持つ5人の開発者は調整の問題です。チームがスケールアップする際に最初に壊れるものと、維持できるセットアップ:コミットされたコンテキストファイル、明確なファイル所有権、爆風半径によるレビュー、そして実際に見えるコストについて説明します。
記事を読む
AgentsRoomをダウンロード
あなたのAIエージェント(Claude、Codex、Antigravity CLI、OpenCode、Aider、Grok Build、Mistral Vibe、Kimi Code)を単一のウィンドウから実行します。
コンパニオンアプリ:外出先でもエージェントを確認
Claude、Codex、Antigravity CLI、またはその他の AI プロバイダーを使用します。
バグや要望を公開バックログに直接送信できます。
実際の AgentsRoom の様子。