Loading...
    • 開発者ガイド
    • API リファレンス
    • MCP
    • リソース
    • リリースノート
    Search...
    ⌘K
    はじめに
    Claude の紹介クイックスタート
    モデルと料金
    モデル概要モデルの選び方Claude 4.6 の新機能移行ガイドモデルの廃止料金
    Claude で構築する
    機能概要Messages API の使用停止理由の処理プロンプトのベストプラクティス
    モデルの機能
    拡張思考適応型思考エフォート高速モード(リサーチプレビュー)構造化出力引用メッセージのストリーミングバッチ処理PDF サポート検索結果多言語サポートエンベディングビジョン
    ツール
    概要ツール使用の実装方法Web 検索ツールWeb フェッチツールコード実行ツールメモリツールBash ツールコンピュータ使用ツールテキストエディタツール
    ツールインフラストラクチャ
    ツール検索プログラムによるツール呼び出しきめ細かいツールストリーミング
    コンテキスト管理
    コンテキストウィンドウコンパクションコンテキスト編集プロンプトキャッシングトークンカウント
    ファイルとアセット
    Files API
    Agent Skills
    概要クイックスタートベストプラクティスエンタープライズ向け SkillsAPI での Skills の使用
    Agent SDK
    概要クイックスタートTypeScript SDKTypeScript V2(プレビュー)Python SDK移行ガイド
    ストリーミング入力リアルタイムでレスポンスをストリーミング停止理由の処理権限の処理ユーザー承認と入力フックによる実行制御セッション管理ファイルチェックポイントSDK での構造化出力Agent SDK のホスティングAI エージェントの安全なデプロイシステムプロンプトの変更SDK での MCPカスタムツールSDK でのサブエージェントSDK でのスラッシュコマンドSDK での Agent Skillsコストと使用量の追跡Todo リストSDK でのプラグイン
    API での MCP
    MCP コネクタリモート MCP サーバー
    サードパーティプラットフォームでの Claude
    Amazon BedrockMicrosoft FoundryVertex AI
    プロンプトエンジニアリング
    概要プロンプトジェネレータープロンプトテンプレートの使用プロンプト改善ツール明確かつ直接的に例を使う(マルチショットプロンプティング)Claude に考えさせる(CoT)XML タグを使うClaude に役割を与える(システムプロンプト)複雑なプロンプトを連鎖させる長文コンテキストのヒント拡張思考のヒント
    テストと評価
    成功基準の定義テストケースの開発評価ツールの使用レイテンシの削減
    ガードレールの強化
    ハルシネーションの削減出力の一貫性を高めるジェイルブレイクの軽減ストリーミング拒否プロンプト漏洩の削減Claude をキャラクターに保つ
    管理とモニタリング
    Admin API 概要データレジデンシーワークスペース使用量とコスト APIClaude Code Analytics APIゼロデータリテンション
    Console
    Log in
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...
    Loading...

    Solutions

    • AI agents
    • Code modernization
    • Coding
    • Customer support
    • Education
    • Financial services
    • Government
    • Life sciences

    Partners

    • Amazon Bedrock
    • Google Cloud's Vertex AI

    Learn

    • Blog
    • Catalog
    • Courses
    • Use cases
    • Connectors
    • Customer stories
    • Engineering at Anthropic
    • Events
    • Powered by Claude
    • Service partners
    • Startups program

    Company

    • Anthropic
    • Careers
    • Economic Futures
    • Research
    • News
    • Responsible Scaling Policy
    • Security and compliance
    • Transparency

    Learn

    • Blog
    • Catalog
    • Courses
    • Use cases
    • Connectors
    • Customer stories
    • Engineering at Anthropic
    • Events
    • Powered by Claude
    • Service partners
    • Startups program

    Help and security

    • Availability
    • Status
    • Support
    • Discord

    Terms and policies

    • Privacy policy
    • Responsible disclosure policy
    • Terms of service: Commercial
    • Terms of service: Consumer
    • Usage policy
    ガイド

    フックでエージェントの動作をインターセプトして制御する

    フックを使用して、主要な実行ポイントでエージェントの動作をインターセプトしカスタマイズする

    Was this page helpful?

    • 通知の送信(TypeScriptのみ)
    • systemMessageが出力に表示されない

    フックを使用すると、エージェントの実行を主要なポイントでインターセプトし、バリデーション、ロギング、セキュリティ制御、またはカスタムロジックを追加できます。フックを使用すると、以下のことが可能です:

    • 破壊的なシェルコマンドや不正なファイルアクセスなど、危険な操作を実行前にブロックする
    • コンプライアンス、デバッグ、または分析のためにすべてのツール呼び出しをログおよび監査する
    • データのサニタイズ、認証情報の注入、またはファイルパスのリダイレクトのために入出力を変換する
    • データベースへの書き込みやAPI呼び出しなどの機密性の高いアクションに対して人間の承認を要求する
    • 状態の管理、リソースのクリーンアップ、または通知の送信のためにセッションのライフサイクルを追跡する

    フックには2つの部分があります:

    1. コールバック関数:フックが発火したときに実行されるロジック
    2. フック設定:SDKにどのイベントにフックするか(PreToolUseなど)、どのツールにマッチするかを指示する

    以下の例は、エージェントが.envファイルを変更するのをブロックします。まず、ファイルパスをチェックするコールバックを定義し、次にWriteまたはEditツールの呼び出し前に実行されるようにquery()に渡します:

    これはPreToolUseフックです。ツールが実行される前に実行され、ロジックに基づいて操作をブロックまたは許可できます。このガイドの残りの部分では、利用可能なすべてのフック、その設定オプション、および一般的なユースケースのパターンについて説明します。

    利用可能なフック

    SDKは、エージェント実行のさまざまな段階に対応するフックを提供します。一部のフックは両方のSDKで利用可能ですが、Python SDKがサポートしていないため、TypeScriptのみのものもあります。

    フックイベントPython SDKTypeScript SDKトリガー条件ユースケースの例
    PreToolUseはいはいツール呼び出しリクエスト(ブロックまたは変更可能)危険なシェルコマンドをブロック
    PostToolUseはいはいツール実行結果すべてのファイル変更を監査証跡に記録
    PostToolUseFailureいいえはいツール実行失敗ツールエラーの処理またはログ
    UserPromptSubmitはいはいユーザープロンプトの送信プロンプトに追加コンテキストを注入

    一般的なユースケース

    フックは多くの異なるシナリオに対応できる柔軟性を持っています。以下は、カテゴリ別に整理された最も一般的なパターンです。

    フックの設定

    エージェントにフックを設定するには、query()を呼び出す際にoptions.hooksパラメータでフックを渡します:

    async for message in query(
        prompt="Your prompt",
        options=ClaudeAgentOptions(
            hooks={
                'PreToolUse': [HookMatcher(matcher='Bash', hooks=[my_callback])]
            }
        )
    ):
        print(message)

    hooksオプションは辞書(Python)またはオブジェクト(TypeScript)で、以下の構造です:

    • キーはフックイベント名(例:'PreToolUse'、'PostToolUse'、'Stop')
    • 値はマッチャーの配列で、それぞれにオプションのフィルターパターンとコールバック関数が含まれます

    フックコールバック関数はイベントに関する入力データを受け取り、エージェントが操作を許可、ブロック、または変更するかを知らせるレスポンスを返します。

    マッチャー

    マッチャーを使用して、どのツールがコールバックをトリガーするかをフィルタリングします:

    オプション型デフォルト説明
    matcherstringundefinedツール名にマッチする正規表現パターン。組み込みツールにはBash、Read、Write、Edit、Glob、Grep、WebFetch、Taskなどがあります。MCPツールはmcp__<server>__<action>パターンを使用します。
    hooksHookCallback[]-

    可能な限り、matcherパターンを使用して特定のツールをターゲットにしてください。'Bash'のマッチャーはBashコマンドに対してのみ実行されますが、パターンを省略するとすべてのツール呼び出しに対してコールバックが実行されます。マッチャーはツール名のみでフィルタリングし、ファイルパスやその他の引数ではフィルタリングしないことに注意してください。ファイルパスでフィルタリングするには、コールバック内でtool_input.file_pathをチェックしてください。

    マッチャーはツールベースのフック(PreToolUse、PostToolUse、PostToolUseFailure、PermissionRequest)にのみ適用されます。Stop、SessionStart、Notificationなどのライフサイクルフックでは、マッチャーは無視され、そのタイプのすべてのイベントに対してフックが発火します。

    ツール名の発見方法: セッション開始時の初期システムメッセージのtools配列を確認するか、マッチャーなしのフックを追加してすべてのツール呼び出しをログに記録してください。

    MCPツールの命名規則: MCPツールは常にmcp__で始まり、その後にサーバー名とアクションが続きます:mcp__<server>__<action>。例えば、playwrightという名前のサーバーを設定した場合、そのツールはmcp__playwright__browser_screenshot、mcp__playwright__browser_clickなどの名前になります。サーバー名はmcpServers設定で使用するキーから取得されます。

    この例では、PreToolUseイベントが発火したときに、ファイル変更ツールに対してのみフックを実行するマッチャーを使用しています:

    options = ClaudeAgentOptions(
        hooks={
            'PreToolUse': [
                HookMatcher(matcher='Write|Edit', hooks=[validate_file_path])
            ]
        }
    )

    コールバック関数の入力

    すべてのフックコールバックは3つの引数を受け取ります:

    1. 入力データ(dict / HookInput):イベントの詳細。フィールドについては入力データを参照
    2. ツール使用ID(str | None / string | null):PreToolUseとPostToolUseイベントを関連付ける
    3. コンテキスト(HookContext):TypeScriptでは、キャンセル用のsignalプロパティ(AbortSignal)を含みます。fetch()などの非同期操作にこれを渡すと、フックがタイムアウトした場合に自動的にキャンセルされます。Pythonでは、この引数は将来の使用のために予約されています。

    入力データ

    フックコールバックの最初の引数には、イベントに関する情報が含まれています。フィールド名はSDK間で同一です(両方ともsnake_caseを使用)。

    共通フィールド(すべてのフックタイプに存在):

    フィールド型説明
    hook_event_namestringフックタイプ(PreToolUse、PostToolUseなど)
    session_idstring現在のセッション識別子
    transcript_pathstring会話トランスクリプトへのパス
    cwdstring現在の作業ディレクトリ

    フック固有のフィールドはフックタイプによって異なります。TSマークの項目はTypeScript SDKでのみ利用可能です:

    フィールド型説明フック
    tool_namestring呼び出されるツールの名前PreToolUse、PostToolUse、PostToolUseFailureTS、PermissionRequestTS
    tool_inputobjectツールに渡される引数PreToolUse、PostToolUse、PostToolUseFailureTS、PermissionRequestTS
    tool_responseanyツール実行から返された結果PostToolUse
    errorstringツール実行失敗のエラーメッセージ

    以下のコードは、tool_nameとtool_inputを使用して各ツール呼び出しの詳細をログに記録するフックコールバックを定義しています:

    コールバックの出力

    コールバック関数は、SDKに処理の進め方を指示するオブジェクトを返します。変更なしで操作を許可するには、空のオブジェクト{}を返します。操作をブロック、変更、またはコンテキストを追加するには、決定を含むhookSpecificOutputフィールドを持つオブジェクトを返します。

    トップレベルフィールド(hookSpecificOutputの外側):

    フィールド型説明
    continuebooleanこのフックの後にエージェントが続行するかどうか(デフォルト:true)
    stopReasonstringcontinueがfalseの場合に表示されるメッセージ
    suppressOutputbooleanトランスクリプトからstdoutを非表示にする(デフォルト:false)
    systemMessagestring

    hookSpecificOutput内のフィールド:

    フィールド型フック説明
    hookEventNamestringすべて必須。現在のイベントにマッチするようにinput.hook_event_nameを使用
    permissionDecision'allow' | 'deny' | 'ask'PreToolUseツールが実行されるかどうかを制御
    permissionDecisionReasonstringPreToolUse決定に対してClaudeに表示される説明
    updatedInput

    この例は、/etcディレクトリへの書き込み操作をブロックしながら、安全なファイル操作についてClaudeに注意を促すシステムメッセージを注入します:

    権限決定フロー

    複数のフックまたは権限ルールが適用される場合、SDKは以下の順序で評価します:

    1. Denyルールが最初にチェックされます(いずれかがマッチ = 即座に拒否)。
    2. Askルールが2番目にチェックされます。
    3. Allowルールが3番目にチェックされます。
    4. 何もマッチしない場合はデフォルトでAskになります。

    いずれかのフックがdenyを返した場合、操作はブロックされます。他のフックがallowを返しても、それをオーバーライドすることはできません。

    ツールをブロックする

    ツールの実行を防止するためにdeny決定を返します:

    ツール入力を変更する

    ツールが受け取る内容を変更するために更新された入力を返します:

    updatedInputを使用する場合、permissionDecisionも含める必要があります。元のtool_inputを変更するのではなく、常に新しいオブジェクトを返してください。

    システムメッセージを追加する

    会話にコンテキストを注入します:

    async def add_security_reminder(input_data, tool_use_id, context):
        return {
            'systemMessage': 'Remember to follow security best practices.'
        }

    特定のツールを自動承認する

    信頼されたツールの権限プロンプトをバイパスします。特定の操作をユーザーの確認なしに実行したい場合に便利です:

    permissionDecisionフィールドは3つの値を受け付けます:'allow'(自動承認)、'deny'(ブロック)、または'ask'(確認を求める)。

    高度なシナリオの処理

    これらのパターンは、複雑なユースケースに対応するより洗練されたフックシステムの構築に役立ちます。

    複数のフックのチェーン

    フックは配列に表示される順序で実行されます。各フックを単一の責任に集中させ、複雑なロジックには複数のフックをチェーンしてください。この例では、すべてのツール呼び出しに対して4つのフックすべてを実行します(マッチャー指定なし):

    正規表現によるツール固有のマッチャー

    正規表現パターンを使用して複数のツールにマッチさせます:

    マッチャーはツール名のみにマッチし、ファイルパスやその他の引数にはマッチしません。ファイルパスでフィルタリングするには、フックコールバック内でtool_input.file_pathをチェックしてください。

    サブエージェントのアクティビティの追跡

    SubagentStopフックを使用してサブエージェントの完了を監視します。tool_use_idは、親エージェントの呼び出しとそのサブエージェントを関連付けるのに役立ちます:

    フックでの非同期操作

    フックはHTTPリクエストなどの非同期操作を実行できます。例外をスローするのではなくキャッチすることで、エラーを適切に処理してください。TypeScriptでは、signalをfetch()に渡すことで、フックがタイムアウトした場合にリクエストがキャンセルされます:

    通知の送信(TypeScriptのみ)

    Notificationフックを使用して、エージェントからのステータス更新を受信し、Slackや監視ダッシュボードなどの外部サービスに転送します:

    TypeScript
    import { query, HookCallback, NotificationHookInput } from "@anthropic-ai/claude-agent-sdk";
    
    const notificationHandler: HookCallback = async (input, toolUseID, { signal }) => {
      const notification = input as NotificationHookInput;
    
      await fetch('https://hooks.slack.com/services/YOUR/WEBHOOK/URL', {
        method: 'POST',
        body: JSON.stringify({
          text: `Agent status: ${notification.message}`
        }),
        signal
      });
    
      return {};
    };
    
    for await (const message of query({
      prompt: "Analyze this codebase",
      options: {
        hooks: {
          Notification: [{ hooks: [notificationHandler] }]
        }
      }
    })) {
      console.log(message);
    }

    よくある問題の修正

    このセクションでは、よくある問題とその解決方法について説明します。

    フックが発火しない

    • フックイベント名が正しく、大文字小文字が区別されていることを確認してください(preToolUseではなくPreToolUse)
    • マッチャーパターンがツール名に正確にマッチしていることを確認してください
    • フックがoptions.hooksの正しいイベントタイプの下にあることを確認してください
    • SubagentStop、Stop、SessionStart、SessionEnd、Notificationフックでは、マッチャーは無視されます。これらのフックはそのタイプのすべてのイベントに対して発火します。
    • エージェントがmax_turnsの制限に達した場合、セッションがフックの実行前に終了するため、フックが発火しないことがあります

    マッチャーが期待通りにフィルタリングしない

    マッチャーはツール名のみにマッチし、ファイルパスやその他の引数にはマッチしません。ファイルパスでフィルタリングするには、フック内でtool_input.file_pathをチェックしてください:

    const myHook: HookCallback = async (input, toolUseID, { signal }) => {
      const preInput = input as PreToolUseHookInput;
      const filePath = preInput.tool_input?.file_path as string;
      if (!filePath?.endsWith('.md')) return {};  // マークダウン以外のファイルをスキップ
      // マークダウンファイルを処理...
    };

    フックのタイムアウト

    • HookMatcher設定のtimeout値を増やしてください
    • TypeScriptでは、3番目のコールバック引数のAbortSignalを使用してキャンセルを適切に処理してください

    ツールが予期せずブロックされる

    • すべてのPreToolUseフックでpermissionDecision: 'deny'を返していないか確認してください
    • フックにロギングを追加して、どのpermissionDecisionReasonを返しているか確認してください
    • マッチャーパターンが広すぎないか確認してください(空のマッチャーはすべてのツールにマッチします)

    変更された入力が適用されない

    • updatedInputがトップレベルではなくhookSpecificOutput内にあることを確認してください:

      return {
        hookSpecificOutput: {
          hookEventName: input.hook_event_name,
          permissionDecision: 'allow',
          updatedInput: { command: 'new command' }
        }
      };
    • 入力の変更を有効にするには、permissionDecision: 'allow'も返す必要があります

    セッションフックが利用できない

    SessionStart、SessionEnd、NotificationフックはTypeScript SDKでのみ利用可能です。Python SDKはセットアップの制限により、これらのイベントをサポートしていません。

    サブエージェントの権限プロンプトが増殖する

    複数のサブエージェントを生成する場合、それぞれが個別に権限を要求する場合があります。サブエージェントは親エージェントの権限を自動的に継承しません。繰り返しのプロンプトを避けるには、PreToolUseフックを使用して特定のツールを自動承認するか、サブエージェントセッションに適用される権限ルールを設定してください。

    サブエージェントによる再帰的なフックループ

    サブエージェントを生成するUserPromptSubmitフックは、それらのサブエージェントが同じフックをトリガーすると無限ループを作成する可能性があります。これを防ぐには:

    • 生成前にフック入力でサブエージェントインジケーターをチェックする
    • parent_tool_use_idフィールドを使用して、すでにサブエージェントコンテキスト内にいるかどうかを検出する
    • フックをトップレベルのエージェントセッションでのみ実行するようにスコープを設定する

    systemMessageが出力に表示されない

    systemMessageフィールドはモデルが参照する会話にコンテキストを追加しますが、すべてのSDK出力モードで表示されるとは限りません。フックの決定をアプリケーションに表示する必要がある場合は、別途ログに記録するか、専用の出力チャネルを使用してください。

    さらに詳しく

    • 権限:エージェントができることを制御する
    • カスタムツール:エージェントの機能を拡張するツールを構築する
    • TypeScript SDKリファレンス
    • Python SDKリファレンス
    import asyncio
    from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher
    
    # ツール呼び出しの詳細を受け取るフックコールバックを定義
    async def protect_env_files(input_data, tool_use_id, context):
        # ツールの入力引数からファイルパスを抽出
        file_path = input_data['tool_input'].get('file_path', '')
        file_name = file_path.split('/')[-1]
    
        # .envファイルを対象としている場合、操作をブロック
        if file_name == '.env':
            return {
                'hookSpecificOutput': {
                    'hookEventName': input_data['hook_event_name'],
                    'permissionDecision': 'deny',
                    'permissionDecisionReason': 'Cannot modify .env files'
                }
            }
    
        # 空のオブジェクトを返して操作を許可
        return {}
    
    async def main():
        async for message in query(
            prompt="Update the database configuration",
            options=ClaudeAgentOptions(
                hooks={
                    # PreToolUseイベント用にフックを登録
                    # マッチャーはWriteとEditのツール呼び出しのみにフィルタリング
                    'PreToolUse': [HookMatcher(matcher='Write|Edit', hooks=[protect_env_files])]
                }
            )
        ):
            print(message)
    
    asyncio.run(main())
    Stop
    はい
    はい
    エージェント実行の停止
    終了前にセッション状態を保存
    SubagentStartいいえはいサブエージェントの初期化並列タスクの生成を追跡
    SubagentStopはいはいサブエージェントの完了並列タスクからの結果を集約
    PreCompactはいはい会話のコンパクション要求要約前に完全なトランスクリプトをアーカイブ
    PermissionRequestいいえはい権限ダイアログが表示される場合カスタム権限処理
    SessionStartいいえはいセッションの初期化ロギングとテレメトリの初期化
    SessionEndいいえはいセッションの終了一時リソースのクリーンアップ
    Notificationいいえはいエージェントのステータスメッセージエージェントのステータス更新をSlackやPagerDutyに送信
    必須。パターンがマッチしたときに実行するコールバック関数の配列
    timeoutnumber60タイムアウト(秒)。外部API呼び出しを行うフックの場合は増やしてください
    PostToolUseFailureTS
    is_interruptboolean失敗が割り込みによって引き起こされたかどうかPostToolUseFailureTS
    promptstringユーザーのプロンプトテキストUserPromptSubmit
    stop_hook_activebooleanストップフックが現在処理中かどうかStop、SubagentStop
    agent_idstringサブエージェントの一意の識別子SubagentStartTS、SubagentStopTS
    agent_typestringサブエージェントのタイプ/ロールSubagentStartTS
    agent_transcript_pathstringサブエージェントの会話トランスクリプトへのパスSubagentStopTS
    triggerstringコンパクションのトリガー:manualまたはautoPreCompact
    custom_instructionsstringコンパクション用に提供されたカスタム指示PreCompact
    permission_suggestionsarrayツールに対する推奨権限更新PermissionRequestTS
    sourcestringセッションの開始方法:startup、resume、clear、またはcompactSessionStartTS
    reasonstringセッションの終了理由:clear、logout、prompt_input_exit、bypass_permissions_disabled、またはotherSessionEndTS
    messagestringエージェントからのステータスメッセージNotificationTS
    notification_typestring通知のタイプ:permission_prompt、idle_prompt、auth_success、またはelicitation_dialogNotificationTS
    titlestringエージェントによって設定されたオプションのタイトルNotificationTS
    async def log_tool_calls(input_data, tool_use_id, context):
        if input_data['hook_event_name'] == 'PreToolUse':
            print(f"Tool: {input_data['tool_name']}")
            print(f"Input: {input_data['tool_input']}")
        return {}
    Claudeが参照できるように会話に注入されるメッセージ
    object
    PreToolUse
    変更されたツール入力(permissionDecision: 'allow'が必要)
    additionalContextstringPreToolUse、PostToolUse、UserPromptSubmit、SessionStartTS、SubagentStartTS会話に追加されるコンテキスト
    async def block_etc_writes(input_data, tool_use_id, context):
        file_path = input_data['tool_input'].get('file_path', '')
    
        if file_path.startswith('/etc'):
            return {
                # トップレベルフィールド:会話にガイダンスを注入
                'systemMessage': 'Remember: system directories like /etc are protected.',
                # hookSpecificOutput:操作をブロック
                'hookSpecificOutput': {
                    'hookEventName': input_data['hook_event_name'],
                    'permissionDecision': 'deny',
                    'permissionDecisionReason': 'Writing to /etc is not allowed'
                }
            }
        return {}
    async def block_dangerous_commands(input_data, tool_use_id, context):
        if input_data['hook_event_name'] != 'PreToolUse':
            return {}
    
        command = input_data['tool_input'].get('command', '')
    
        if 'rm -rf /' in command:
            return {
                'hookSpecificOutput': {
                    'hookEventName': input_data['hook_event_name'],
                    'permissionDecision': 'deny',
                    'permissionDecisionReason': 'Dangerous command blocked: rm -rf /'
                }
            }
        return {}
    async def redirect_to_sandbox(input_data, tool_use_id, context):
        if input_data['hook_event_name'] != 'PreToolUse':
            return {}
    
        if input_data['tool_name'] == 'Write':
            original_path = input_data['tool_input'].get('file_path', '')
            return {
                'hookSpecificOutput': {
                    'hookEventName': input_data['hook_event_name'],
                    'permissionDecision': 'allow',
                    'updatedInput': {
                        **input_data['tool_input'],
                        'file_path': f'/sandbox{original_path}'
                    }
                }
            }
        return {}
    async def auto_approve_read_only(input_data, tool_use_id, context):
        if input_data['hook_event_name'] != 'PreToolUse':
            return {}
    
        read_only_tools = ['Read', 'Glob', 'Grep', 'LS']
        if input_data['tool_name'] in read_only_tools:
            return {
                'hookSpecificOutput': {
                    'hookEventName': input_data['hook_event_name'],
                    'permissionDecision': 'allow',
                    'permissionDecisionReason': 'Read-only tool auto-approved'
                }
            }
        return {}
    options = ClaudeAgentOptions(
        hooks={
            'PreToolUse': [
                HookMatcher(hooks=[rate_limiter]),        # 最初:レート制限をチェック
                HookMatcher(hooks=[authorization_check]), # 2番目:権限を検証
                HookMatcher(hooks=[input_sanitizer]),     # 3番目:入力をサニタイズ
                HookMatcher(hooks=[audit_logger])         # 最後:アクションをログ
            ]
        }
    )
    options = ClaudeAgentOptions(
        hooks={
            'PreToolUse': [
                # ファイル変更ツールにマッチ
                HookMatcher(matcher='Write|Edit|Delete', hooks=[file_security_hook]),
    
                # すべてのMCPツールにマッチ
                HookMatcher(matcher='^mcp__', hooks=[mcp_audit_hook]),
    
                # すべてにマッチ(マッチャーなし)
                HookMatcher(hooks=[global_logger])
            ]
        }
    )
    async def subagent_tracker(input_data, tool_use_id, context):
        if input_data['hook_event_name'] == 'SubagentStop':
            print(f"[SUBAGENT] Completed")
            print(f"  Tool use ID: {tool_use_id}")
            print(f"  Stop hook active: {input_data.get('stop_hook_active')}")
        return {}
    
    options = ClaudeAgentOptions(
        hooks={
            'SubagentStop': [HookMatcher(hooks=[subagent_tracker])]
        }
    )
    import aiohttp
    from datetime import datetime
    
    async def webhook_notifier(input_data, tool_use_id, context):
        if input_data['hook_event_name'] != 'PostToolUse':
            return {}
    
        try:
            async with aiohttp.ClientSession() as session:
                await session.post(
                    'https://api.example.com/webhook',
                    json={
                        'tool': input_data['tool_name'],
                        'timestamp': datetime.now().isoformat()
                    }
                )
        except Exception as e:
            print(f'Webhook request failed: {e}')
    
        return {}

    出力がどのフックタイプのものかを識別するために、hookSpecificOutputにhookEventNameを含めてください