Claude Code の hook と言えば、.claude/settings.json にシェルスクリプトのパスを書くもの。多くの設定例がそうです。ただ v2.1.161 の hook handler には type が5種類あって、シェルを書かずに済む選択肢が増えています。command だけ使っているなら、半分も使えていません。
5つのハンドラータイプ早見表
公式リファレンスの “Hook handler fields” セクションは冒頭でこう言い切っています。“There are five types”。command / http / mcp_tool / prompt / agent の5つです。/hooks メニューの説明にも “The menu displays all five hook types” と明記されています。
| type | 実行されるもの | 判断の主体 | timeoutの既定値 |
|---|---|---|---|
command | シェルコマンド | あなたのスクリプト | 600秒 |
http | URLへのPOST | 受け側のサーバー | 600秒 |
mcp_tool | 接続済みMCPサーバーのツール | そのツール | 600秒 |
prompt | fast modelへの単発評価 | Claudeモデル | 30秒 |
agent | ツール付きサブエージェント | サブエージェント | 60秒 |
分かれ目は「判断の主体」です。固定ルールならスクリプト。文脈を読んだ判断が要るなら prompt。コードベースを調べる必要があるなら agent。timeout の既定値もそこに連動していて、prompt は30秒、agent は60秒、残り3つは600秒と差がついています。
command型: スクリプトで完結する定型処理
最初に挙げた command。スクリプトが stdin で JSON を受け取り、exit code と stdout で結果を返します。保存のたびに linter を走らせる、といった定型処理に向きます。
matcherとパスの基本形
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check-style.sh",
"args": []
}
]
}
]
}
}
matcher がツール名にマッチしたとき発火します。${CLAUDE_PROJECT_DIR} はプロジェクトルートに展開されるので、作業ディレクトリがどこでもスクリプトを見つけられます。登録後に /hooks を開くと、こう見えます。
PostToolUse (1)
Write|Edit
[command] Project · .claude/hooks/check-style.sh
[command] の接頭辞と、定義元(Project = .claude/settings.json)が並びます。どの設定ファイル由来かをこの画面で確認できます。
exec形式とshell形式
args を付けると exec 形式になり、シェルを介さず直接プロセスを起動します。パスにスペースや特殊文字が混ざっても、クオートが要りません。逆にパイプや && を使いたいときは args を省いて shell 形式にします。パス参照を含む hook は exec 形式が推奨されています。
prompt型: fast modelに判断させる
スクリプトで書けるのは固定ルールまで。「この編集は機密情報を含むか」のような文脈依存の判断は、正規表現では崩れます。そこで prompt 型。公式の定義は “send a prompt to a Claude model for single-turn evaluation. The model returns a yes/no decision as JSON” です。単発でモデルに投げ、yes/no を JSON で受け取ります。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "prompt",
"prompt": "次の編集がAPIキーや認証情報のハードコードを含むなら decision:block を返して。含まないなら何もしないで。\n\n$ARGUMENTS"
}
]
}
]
}
}
$ARGUMENTS が hook の入力 JSON を差し込むプレースホルダです。”Use $ARGUMENTS as a placeholder for the hook input JSON” と “Prompt and agent hook fields” の表に書かれています。モデルは model フィールドを省くと “Defaults to a fast model” 、つまり既定で fast model が選ばれます。Haiku 相当の軽いモデルが単発で判定するので、command 型に近い感覚で回せます。
スクリプトを書かずに判断を挟める
命名規則のチェックや、コミットメッセージの妥当性判定のように、ルールを自然言語で書いたほうが速いものがあります。正規表現でゴリゴリ書くより、prompt に1行書くほうが保守も楽。厳密な精度より、ルールを読みやすく保ちたい場面に向きます。
agent型: ツールを持たせて調べさせる
prompt 型は単発評価なので、ファイルを読んだり grep したりはできません。「変更したファイルに対応するテストが存在するか」を確かめるには、コードベースを実際に調べる必要があります。これが agent 型の領域です。
{
"type": "agent",
"prompt": "変更されたソースに対応するテストファイルが存在するか Grep で確認して。1つでも欠けていれば decision:block で指摘して。\n\n$ARGUMENTS"
}
公式の定義は “spawn a subagent that can use tools like Read, Grep, and Glob to verify conditions before returning a decision”。Read / Grep / Glob を使って条件を検証してから判断を返します。ただし同じ文に “Agent hooks are experimental and may change” と添えられています。実験的扱いなので、本番の品質ゲートに据えるなら仕様変更を覚悟しておきます。
5つの中で一番重い型です。timeout の既定が60秒なのも、複数ターンの調査を見込んでいるから。毎回の編集に挟むより、コミット前やセッション終了時のような節目に置くのが現実的です。
HTTP型とMCP tool型
残り2つはチーム運用と既存リソースの再利用が軸です。文章で説明するより表で済みます。
| type | 向く場面 | ブロックの仕方 | 失敗時の扱い |
|---|---|---|---|
http | チーム全員に同じルールを強制したい。サーバー1台で全員のClaudeが従う | 2xxレスポンスで decision:"block" を返す | 非2xx・接続失敗・timeoutはすべて non-blocking error で続行 |
mcp_tool | 接続済みMCPサーバーのツールを流用したい | ツールのテキスト出力がJSONなら判定として処理 | サーバー未接続や isError:true は non-blocking error で続行 |
http 型の注意点は、エラーが全部 non-blocking になること。サーバーが落ちていてもツール呼び出しは止まりません。強制力を期待するなら、サーバーの可用性込みで設計します。mcp_tool 型は接続済みのサーバーが前提で、hook が OAuth や接続フローを起動することはありません。SessionStart はサーバー接続前に発火しがちなので、初回は “not connected” を想定しておきます。
Stop hookで「やり残し」を止める
Claude が「完了しました」と応答を終える瞬間に発火するのが Stop hook。リファレンスの定義は “When Claude finishes responding”。テストを流さずに完了報告された、という事故をここで止められます。5種類のうち、運用の効きが一番大きいのがこの型です。
実際、リファクタリングを頼んだあと、テスト未実行のまま「完了しました」で止まったことがありました。差分は正しく見えても、テストを通した証跡がない。これを毎回人間が確認するのは続きません。そこで Stop hook です。
decision:blockで続行を促す
“Stop decision control” の表によれば、返せるフィールドは2つだけ。decision に "block" を入れると Claude は停止せず続行し、reason でその理由を伝えます。reason は block 時には必須です。
#!/bin/bash
# .claude/hooks/require-tests.sh
input=$(cat)
# 直近のテスト成功マーカーが無ければ続行させる
if [ ! -f .test-passed ]; then
cat <<'JSON'
{"decision":"block","reason":"テスト未実行のまま停止しようとしています。npm test を流して .test-passed を作ってから完了してください"}
JSON
fi
exit 0
このスクリプトを Stop イベントに登録すると、マーカーが無いまま停止しようとしたときに Claude へ理由が戻り、作業が続きます。実際の挙動はこうなります。
> 完了しました。リファクタリングを反映しています。
[Stop hook] テスト未実行のまま停止しようとしています。npm test を流して...
> npm test を実行します。
PASS src/parser.test.ts
> テストが通りました。.test-passed を作成し、改めて完了します。
無限ループにしない安全装置
条件が永遠に満たされないと、block が延々と繰り返されます。これに対して2段の歯止めがあります。1つは入力に含まれる stop_hook_active フィールド。これが true なら、すでに stop hook 起因で続行中という意味なので、判定をスキップできます。もう1つは Claude Code 側の打ち切りで、リファレンスは "Claude Code overrides the hook and ends the turn after 8 consecutive blocks" と書いています。8回連続で block すると、強制的にターンが終わります。
/goalという既製ショートカット
「条件を満たすまで作業を続けさせたい」だけなら、hook を書かなくても済みます。リファレンスの Tip にある通り、/goal コマンドが "a built-in shortcut for a session-scoped prompt-based Stop hook" です。セッション限定の prompt 型 Stop hook を裏で組んでくれます。設定ファイルを触らず、その場限りの完了条件を指定したいときの近道です。
exit codeの罠: 1ではブロックされない
command 型で一番ハマるのがここ。Unix の慣習で「失敗は exit 1」と書くと、hook は素通りします。
# アンチパターン: lintが落ちてもツール実行は止まらない
eslint "$file" || exit 1
# OK: ブロックしたいなら exit 2
eslint "$file" || exit 2
リファレンスの Warning がそのまま理由です。"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. If your hook is meant to enforce a policy, use exit 2"。exit 2 だけがアクションをブロックし、stderr の内容が Claude にエラーとして戻ります。exit 1 やその他の非ゼロは non-blocking 扱いで、警告は出ても処理は進みます。
例外は1つだけ。WorktreeCreate では、どの非ゼロ exit code でも worktree 作成が中止されます。それ以外のイベントでポリシーを効かせるなら exit 2、と覚えておけば足ります。
まとめ
hook = シェルスクリプト、という前提を外すと設計の幅が広がります。要点を整理します。
typeはcommand/http/mcp_tool/prompt/agentの5種類。判断の主体で選ぶ- 固定ルールは
command、文脈依存の判断はprompt(fast model・単発)、コードベース調査はagent(experimental) - チーム共通の強制は
http、既存MCPツールの流用はmcp_tool。どちらも失敗は non-blocking Stophook +decision:"block"でやり残しを止められる。8連続blockで自動打ち切り、手軽な版は/goal- command 型でブロックしたいなら
exit 2。exit 1は素通りする
まず command から始めて、判断が要る場面で prompt、調査が要る場面で agent に上げていく。この順で広げるのが、5種類を無理なく使い分ける現実的なルートです。

