Claude Codeで一度でも hook を設定したことがあり、command 以外の type を試したい人向け
command type で1行スクリプトを書く範囲を超えて、社内サーバの判定 API を噛ませたい・Claude モデルに「これ破壊的?」を即答させたい・サブエージェントにファイル中身を読ませてから判定させたい、という運用要求が出てきた段階で、用途別に http / prompt / agent を使い分けて settings.json の hooks 配列に書き加える場面で使います
Claude Code のフックは登録すると「Bash を叩く直前」「Edit が終わった直後」みたいなタイミングで何かを差し込める仕組みです。普段の type: "command" はそのタイミングでローカルのスクリプトを叩く形ですが、別マシンの判定サーバに投げたり、別の Claude モデルに「これ危険じゃない?」と聞きに行ったり、サブエージェント(小さい Claude)を起動して周辺コードを読ませてから判定したり、というやり方もできます。これが http / prompt / agent の3 type です。
用途別にざっくり分けると、http は社内サーバや SaaS の API を噛ませたい時、prompt は単発の yes/no 判定を Claude モデルに任せたい時、agent はファイルを読ませた上で判定させたい時。それぞれ落とし穴が違うので、3つ並べて見ていきます。
噛み砕くと
会社の入退室で例えると、警備員が「この人入れていい?」と判断する方法が type の違いです。
command は警備員のマニュアル本(ローカルスクリプト)を引く方式。http は本社の総務部に内線で問い合わせる方式。prompt は警備員自身が「常識的に考えてセーフかアウトか」その場で即答する方式。agent は警備員が一旦立ち会いを保留して、過去の入退室ログを書庫から探してきて読んでから判定する方式。
判定が速くて済むなら prompt、調べ物が要るなら agent、社内システムと連携したいなら http、と分けるイメージです。
大事な前提:3 type とも「ブロックしたい時の返し方」が違う
command は exit code 2 + stderr でブロック。http と prompt / agent は 2xx の HTTP レスポンス(または stdout)に「ブロック指示の JSON」を入れて返す形ですが、JSON の中身がイベントごとに違います。PreToolUse なら hookSpecificOutput.permissionDecision: "deny"。PostToolUse / Stop / ConfigChange / PreCompact など他のイベントなら Top-level decision: "block"。混ぜると静かに無視されます。
特に http は要注意で、「サーバが落ちたら自動でブロックされる」と思いがちですが逆です。接続失敗・タイムアウト・4xx・5xx は全部「non-blocking error」になって、ツール実行は普通に通ります。「セキュリティのために HTTP hook を入れたつもりが、サーバ停止中はノーガード」みたいな事故が起きやすい。
「PR を出す前に Bash の安全性チェック」を例に、3 type を使い分ける
今回は3つの題材で、http / prompt / agent をそれぞれ実演します。同じ「Bash を叩く前にチェック」をやらせる場面でも、選ぶ type で書き方が全然違います。
シナリオA(http):社内 webhook で「インシデント DB」と照合する
状況としては、社内に「過去にやらかしたコマンドリスト」を管理する webhook サーバが既にある想定です。たとえば rm -rf node_modules を間違えて本番で叩いた履歴とか、似たパターンを再発させないために DB 化してあるやつ。
Claude Code が Bash を叩く直前に、コマンド文字列をその webhook に POST して、「過去のヤバいパターンに似てるか」を判定してもらう構成です。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "http",
"url": "http://localhost:8080/hooks/pre-tool-use",
"timeout": 30,
"headers": {
"Authorization": "Bearer $MY_TOKEN"
},
"allowedEnvVars": ["MY_TOKEN"]
}
]
}
]
}
}
ここで初心者がやりがちな勘違いがあります。headers に $MY_TOKEN と書いただけでは、その値は読み込まれません。allowedEnvVars に "MY_TOKEN" を入れた時だけ中身が展開されます。リストに入れ忘れると黙って空文字が入って、サーバ側に Bearer ヘッダなしで届く。気づくのは大抵 401 を食らった時です。これは罠。
サーバが返すべき JSON はこういう形です。今回は PreToolUse なので、Top-level decision ではなく hookSpecificOutput.permissionDecision: "deny" を使う点に注意します:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "rm -rf を含むコマンドは禁止リストにマッチしました"
}
}
セーフな時は単に 2xx を返して空ボディか {} でも大丈夫。何も決定しなければそのままツール実行に進みます。PostToolUse や Stop など他のイベントで HTTP hook を使う場合は Top-level decision: "block" を返す形になります。混同しないこと。
シナリオB(prompt):別の Claude モデルに「destructive かどうか」を即答させる
シナリオAと同じく Bash の PreToolUse ですが、こちらは外部サーバを立てる手間が要らない構成。Claude モデル本人に「これ破壊的なコマンドだと思う?」と聞きに行きます。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "prompt",
"prompt": "次の Bash コマンドは破壊的か判定してください。破壊的の例:rm、DROP TABLE 等。破壊的なら以下の JSON だけを返してください。前置きや説明は禁止。{\"hookSpecificOutput\": {\"hookEventName\": \"PreToolUse\", \"permissionDecision\": \"deny\", \"permissionDecisionReason\": \"理由\"}} 安全なら {} だけを返してください。コマンド入力: $ARGUMENTS",
"timeout": 30
}
]
}
]
}
}
$ARGUMENTS のところに、フックが受け取る入力 JSON 全部が文字列として入ります。tool_input.command もこの JSON に含まれます。受け側のモデルは1ターン推論して JSON を返す形。
注意点が2つ。1つ目は、モデルが返した文字列が JSON としてパースできないと non-blocking error 扱いになって、ブロックされず素通りします。だから prompt の文中で「必ず JSON で、このスキーマで返せ」と明示するのが必須。PreToolUse のブロック JSON は {"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "..."}} の形なので、上のサンプルプロンプトの {"decision": "block" | "allow"} という指示はそのままだと PreToolUse では効きません。プロンプトを「上記スキーマで返せ」に書き換える必要があります。「考えを書いた後に JSON を返してください」みたいな曖昧な指示は危険です。
2つ目は時間制限。prompt の既定は30秒、agent は60秒。これを超えると hook 全体が時間切れ扱いになります。重い判定を書こうとして長文プロンプトにすると詰まる。短く、判定だけに絞ると安定します。
シナリオC(agent):サブエージェントに変更履歴を読ませて書式違反を見つけさせる
3つめは、ファイルを読ませた上で判定が必要な場面。Edit / Write が終わった直後(PostToolUse)に、サブエージェントを起動して git log -20 --oneline で過去の変更履歴を取得させ、編集ファイル周辺を Grep させて、「過去の変更履歴メッセージの書式違反パターンがまた紛れ込みそうか」を判定する例です。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "agent",
"prompt": "今変更されたファイルの周辺コードを Read で確認し、git log -20 --oneline を Bash で取得して、最近の変更履歴メッセージが社内書式に従っているか判定してください。社内書式は先頭に [TICKET-番号] 必須。違反があれば hookSpecificOutput.additionalContext に「次の変更履歴メッセージは [TICKET-番号] を先頭に付けること」と返してください。入力: $ARGUMENTS",
"timeout": 90
}
]
}
]
}
}
PostToolUse は仕様上「ツール実行を止める」ことができません(もう実行は終わってる)。なので block ではなく、hookSpecificOutput.additionalContext 経由でメインの Claude に追加情報として渡す形になります。「直してから次のステップへ進め」と読ませる導線。
ここで agent type の experimental(実験的)ラベルが効いてきます。公式 docs にもはっきり「may change(仕様が変わる可能性あり)」と書いてあるので、業務クリティカルな自動化を agent hook に乗せるのはまだ早い。社内の試験運用や個人開発で使う段階かなと思っています。
つまり http / prompt / agent はそれぞれ何をしてくれるのか
- http がやってくれる:社内サーバや SaaS 側のロジックで判定させる。判定結果は 2xx + JSON で返す。env 変数を header に差し込める。ただし
allowedEnvVarsは必須 - http がやってくれない:サーバ停止時の自動ブロック。落ちたらツール実行は素通りする
- prompt がやってくれる:単発の yes/no 判定を Claude モデルに任せる。サーバ不要
- prompt がやってくれない:ファイル中身を見て判定する作業。1ターン推論なので調査系はできない
- agent がやってくれる:サブエージェントを立ち上げて Read / Grep / Glob で周辺を調べてから判定
- agent がやってくれない:本番自動化での安定動作の保証。experimental なので仕様変動リスクあり
- 3 type 共通で意味が薄い場面:ローカルファイルを1個チェックすればいいだけの単純判定。それなら command type に書いた数行のスクリプトで十分
使いどころ3シナリオ(具体題材で再現)
シナリオ1:チーム共通の禁止コマンドリストを「中央サーバ」で管理したいとき → http
禁止リストをメンバー全員のローカル設定にコピーして配るのは現実的じゃないし、更新も追えません。中央の webhook サーバに DB を持たせて、各人の settings.json には HTTP hook の URL だけ書いておく構成にすると、リスト更新は1か所で済みます。社内 VPN や Tailscale 経由の http://internal-host:8080/... を URL に書いて、Bearer token を allowedEnvVars 経由で渡す。落ちた時は素通りすると分かった上で、別の command hook(ローカル regex)と二段構えにするのが安全策です。
シナリオ2:「危険コマンドの定義」自体を Claude の判断に任せたいとき → prompt
禁止ワードを正規表現で書ききるのは大変で、漏れも起きます。「破壊的と感じるか」を毎回 Claude モデルに即答させると、新種の危険コマンドにも反応する余地が出る。たとえば rm -rf ~/Documents を見たことなくても、文脈で「これはまずい」と判断できる。fast model でも30秒以内に返ってくるので、Bash 1発のレイテンシ追加は許容範囲かなと思います。
シナリオ3:Edit / Write の後に「コードの整合性」「変更履歴の書式」など複数情報を読んで判定したいとき → agent
たとえば「edit 後のファイルが既存のコーディング規約に沿っているか」を判定したい時、ファイル本体 + DESIGN.md + 周辺の似たファイル、を全部読んでから判定する必要があります。これは prompt の1ターン推論では無理で、agent type のサブエージェントが Read / Grep を駆使して調べる構造になります。社内 lint ルールを CLAUDE.md にまとめて、それを参照させる形で運用するチームもあります。ただし experimental なので、業務クリティカル自動化は避けるのが安全。
初心者が踏みやすい落とし穴
- HTTP hook の非2xx は全部 non-blocking。「セキュリティのためにサーバ立てたから安心」は誤解。接続失敗・タイムアウト・4xx・5xx は全部素通りして、ツールは実行されます。本気でブロックしたいなら 2xx を返したうえで JSON ボディに「ブロック指示」を入れる必要があります。
PreToolUseならhookSpecificOutput.permissionDecision: "deny"、PostToolUse/Stopなど他イベントなら Top-leveldecision: "block" $MY_TOKENを headers に書いただけでは展開されない。allowedEnvVars: ["MY_TOKEN"]を明示しないと空文字になって、Bearer ヘッダなしで POST される。401 を食らうまで気づきにくい- agent hooks は experimental(公式明記)。仕様が将来変わる可能性があると docs に書いてある。プロダクション運用に組み込む前に、自分のチームの試験範囲に留めておく方が安全
- prompt は既定30秒、agent は既定60秒で時間切れ。重いプロンプトや大量ファイル読み込みを agent にやらせると詰まる。短く・判定だけに絞った設計にしないと不安定
- prompt の出力が JSON パース不能だと non-blocking error。「考えてから JSON を返して」みたいな曖昧プロンプトだと、モデルが前置きを返した瞬間にパース失敗して素通りする。「JSON だけ返せ、他の文字を含めるな」と強く指示すること
- 同じ URL の HTTP hook を複数 matcher で登録しても dedup される。「念のため Edit と Write で二重登録」しても URL で重複排除されるので、二重実行はされない。複数回叩きたいなら URL を変える必要がある
$CLAUDE_CODE_REMOTEでローカル / web を分岐できる。claude.ai の web 環境では"true"、ローカルでは未設定。サーバ側ハンドラでこの値を見て挙動を変える運用もできる- 5つ目の type
mcp_toolの存在。今回は触れていませんが、すでに繋がっている MCP サーバのツールを叩く type もあります。MCP 経由で社内ナレッジに問い合わせる用途。SessionStart / Setup の段階ではまだサーバが繋がっていない可能性が高く、最初の発火で「未接続」エラーになりがちなのが注意点
書き方
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "http", "url": "http://...", "timeout": 30, "headers": {"Authorization": "Bearer $TOKEN"}, "allowedEnvVars": ["TOKEN"] },
{ "type": "prompt", "prompt": "...$ARGUMENTS", "timeout": 30 },
{ "type": "agent", "prompt": "...$ARGUMENTS", "timeout": 90 }
]
}
]
}
}
やってみるとこうなる
入力
Bash で `rm -rf node_modules` を叩こうとした時、PreToolUse の prompt hook が発火し、Claude モデルに「次の Bash コマンドは破壊的か判定し、破壊的なら hookSpecificOutput.permissionDecision=deny の JSON だけを返してください: $ARGUMENTS」と1ターン推論で送られる
出力例
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "rm -rf によるディレクトリ再帰削除は不可逆操作のため停止しました"
}
}
この JSON がモデル(または http hook のサーバ)から返ると、Claude Code は PreToolUse のツール呼び出しをブロックし、permissionDecisionReason をユーザーに表示します。PostToolUse や Stop など他イベントの場合は Top-level {"decision":"block","reason":"..."} 形式に変える必要があります。
このページに出てきた言葉
- hook
- Claude Code が決まったタイミング(ツール実行前/後、セッション開始時など)で自動的に差し込める処理。<code>settings.json</code> の <code>hooks</code> 欄に登録する
- type
- そのフックの処理方式。command / http / mcp_tool / prompt / agent の5種類がある
- matcher
- そのフックを当てるツール名やパターンの絞り込み。<code>"Bash"</code> や <code>"Edit|Write"</code> のように書く
- $ARGUMENTS
- prompt / agent type の <code>prompt</code> 文中に置く差し込み場所。フックが受け取る入力 JSON 全部がここに展開される
- allowedEnvVars
- http type の <code>headers</code> 内で <code>$TOKEN</code> のような書き方を有効化するための明示リスト。書かないと展開されず空文字になる
- non-blocking error
- フックでエラーが起きても、ツール実行を止めずに続行する挙動。http の非2xx・接続失敗・タイムアウトはすべてこの扱い
- decision / permissionDecision
- フックがツール実行を止めるかを伝える応答 JSON のキー。PreToolUse は <code>hookSpecificOutput.permissionDecision: "deny"</code>、PostToolUse / Stop / ConfigChange など他イベントは Top-level <code>decision: "block"</code> を使う。混ぜると無視される
- hookSpecificOutput.additionalContext
- PostToolUse のようにツール実行を止められないイベントで、メインの Claude に追加情報を渡す箱
- experimental
- 実験的機能を意味するラベル。公式 docs で「may change」と注記されており、本番運用は避けるのが安全。agent type がこれに該当する
- $CLAUDE_CODE_REMOTE
- claude.ai の web 環境では <code>"true"</code> がセットされ、ローカル CLI では未設定になる設定値。ハンドラ側で local / remote を分岐するのに使える