原典: React to events / 日本語版
一行要約
mod の hook は Claude Code が動く前に走るミドルウェアの鎖で、見る(next(e) を返す)・書き換える(変えたコピーを next に渡す)・自分で答える(next を呼ばない)のどれかを選ぶ。止める用途の hook は、失敗すると既定で素通り(fail open)になるので .catch で fail closed にする。
要点
3つの扱い方
- observe —— 自分の処理をして
next(e)を返す。後で何かするならawait next(e)してから - rewrite —— イベントは深く凍結されていて、代入すると例外。変えたコピーを
nextに渡す。結果のほうを変えるならawait next(e)のあとでコピーを返す - answer ——
nextを呼ばずに結果を返す。鎖が切れ、後の mod も Claude Code 自身の動作も走らない
matcher(on の第2引数)でイベントを絞れる。値・配列・正規表現が使え、全フィールドが一致したときだけ走る。同じイベントを matcher 無しで2回登録すると、モジュールが読み込まれない。
ツール呼び出しを守る・変える(tool.call)
subagent と MCP ツールの呼び出しも含め、ツールを走らせる直前に発火する。next(e) を呼ぶと、Claude Code が permission の検査をしてからツールを走らせる。
When you do that, no permission prompt appears and the tool doesn’t run, so the result you return is all Claude learns about the call.
(
nextを呼ばずに{ result: ... }を返すと)確認プロンプトは出ず、ツールも走らない。返した結果が、その呼び出しについて Claude が知るすべてになる。
{ deny: '...' } を返すと、Claude はその文をツールの結果として読む —— Claude が行動できる指示として書く。
managed settings の hook は、どの mod の tool.call よりも先に走り、そのブロックは最終。
ユーザーが決めるまで保留する —— hook は next を呼ぶ前に await でき、その間ツール呼び出しは保留される。$.ui.ask で選択肢を出す。待つのは mods API の呼び出しの中で —— その時間は hook の時間制限に数えられないが、自前の promise を待つ時間は数えられ、時間切れの hook は飛ばされて保留中のコマンドが走る。claude -p では $.ui.ask が reject する。
承認・拒否を決める(tool.check)
It fires after the permission rules and the settings hooks have decided, and
next(e)resolves to their decision:allow,ask, ordeny. Your hook returns that decision or a different one.permission ルールと settings hook が決めた後に発火し、
next(e)はその決定(allow/ask/deny)に解決される。hook はその決定か、別の決定を返す。
固定のコマンドやパスなら permission ルールで足りる。判断がその時点の状態(今の git ブランチ、別の hook が記録した値)に依存するときに使う。PreToolUse hook(managed 以外)が止めた呼び出しも承認できる(どの決定が mod に勝つかは permissions)。
The hook matches the text of the command, so treat it as a reminder for Claude. To block pushes to
mainfor everyone, protect the branch on your Git host.この hook はコマンドの文字列に当てているので、Claude への注意喚起として扱え。全員に対して
mainへの push を止めたいなら、Git のホスト側でブランチを保護せよ。
プロンプトとターン
prompt.submit——next({ ...e, text })で書き換え(transcript にも新しい文が出る)、contextに足すと Claude だけが読む文を後ろに付けられる、{ drop: '...' }で送らない。system prompt の節や最初の context、skill の文を変える hook の出力が、リクエストごとに変わると prompt cache が壊れるturn.start/turn.step/turn.complete——turn.stepはモデルへのリクエストごと(ツール呼び出しのあるターンは複数回)。トークン使用量を読む、next({ ...e, model })で別のモデルに送る、モデルを呼ばずに答えるができる。ストリームするので async generator で書き、yield* next(e)。subagent のリクエストにも走るので、メインだけならe.agentIdを見る- settings hook のイベントは
classic.Stopのようにclassic.を付けた名前で扱える
mod が走る順序
鎖の外側が先に見て、結果を後で見る。後の mod は前の mod がイベントを見るのを止められない。
- 組み込みのガード
sec-default@builtin(読み込まれる環境で)、組織のprependPlugins、その他の組織の mod - ユーザーが入れた mod(
dependenciesに挙げた mod より先) - 組織の
appendPlugins - その他の組み込み mod
settings hook の位置: managed の PreToolUse hook は最初の mod の tool.call より前で、ブロックは最終(どの mod も見ない)。それ以外の設定ファイルと plugin の PreToolUse hook は、最後の mod が next を呼んだ後に Claude Code 自身の動作の一部として走る —— next を呼ばずに答える mod はそれらを走らせない。 tool.check はそれらの後に発火するので、それらが止めたものも承認できる。
hook が失敗したら
It failed before calling
next: Claude Code skips it, and the next handler runs in its place
nextを呼ぶ前に失敗した場合: Claude Code はそれを飛ばし、次の handler が代わりに走る
例外・時間切れ・形の違う結果で失敗した hook は、next を呼ぶ前なら飛ばされて素通りする(next が解決した後なら、その結果が残る)。呼び出しを止める hook は .catch を付け、失敗したら { deny } を返して fail closed にする —— 付けないと、守っていたコマンドが走る。
そのまま使える具体例
# 失敗したときの1行(どこで読めるかはセッションによる)
my-mod: tool.call hook skipped: threw Error: boom原典のコード例は次の形を示している(コードは原典を見る): git push --force を deny で止める tool.call、.mdx の編集を $.ui.log で記録する tool.call、危険なコマンドを $.ui.ask で保留する tool.call、main での git push を拒否する tool.check、PR に触れたプロンプトに今のブランチを足す prompt.submit、リクエストごとの cache 読み書きを記録する turn.step、.catch で fail closed にする guard。
原典で言及されている関連文書
- mods-overview — mod の全体像
- mods-create — 最初の mod
- mods-admin —
prependPlugins/appendPluginsと組み込みのガード - permissions — どの決定が mod に勝つか
- hooks — settings hook のイベント
- prompt-caching — prompt を変える hook と cache