Hook Types (HTTP / Prompt / Agent)(フックタイプ:エイチティーティーピー・プロンプト・エージェント)

フック
Hook Types (HTTP / Prompt / Agent)
フックタイプ:エイチティーティーピー・プロンプト・エージェント
Claude Code のフックには5つの type があり、そのうち http はサーバへ POST して判定を返す方式、prompt は Claude モデルに単発で yes/no 判定を即答させる方式、agent はサブエージェント(小さい Claude)を起動して Read / Grep / Glob で周辺を調べてから判定させる方式です。command 以外の3 type を使うと、ローカルスクリプトを書かずにフック判定を外出しできます

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 を返して空ボディか {} でも大丈夫。何も決定しなければそのままツール実行に進みます。PostToolUseStop など他のイベントで 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-level decision: "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 を分岐するのに使える

関連項目

公式ドキュメント

https://code.claude.com/docs/en/hooks

-

← 戻る