SpyBara
Go Premium

agent-sdk/hooks.md 2026-09-08 20:00 UTC to 2026-09-09 22:58 UTC

This page contains 71 additions and 50 deletions.

2026
Wed 9 22:58 Fri 18 23:58

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

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

フックはエージェントイベントに応答してコードを実行するコールバック関数です。ツールが呼び出されたり、セッションが開始したり、実行が停止したりするなどのイベントに対応します。フックを使用すると、以下のことができます。

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

フックの仕組み

1

イベントが発火する

エージェント実行中に何かが起こり、SDK がイベントを発火します。ツールが呼び出されようとしている(PreToolUse)、ツールが結果を返した(PostToolUse)、サブエージェントが開始または停止した、エージェントがアイドル状態である、または実行が完了したなどです。イベントの完全なリストを参照してください。

2

SDK が登録されたフックを収集する

SDK は、そのイベントタイプに登録されたフックをチェックします。これには、options.hooks に渡すコールバックフックと、対応する settingSources または setting_sources エントリが有効になっているときの設定ファイルからのシェルコマンドフックが含まれます。これはデフォルトの query() オプションで有効になっています。

3

マッチャーがどのフックを実行するかをフィルタリングする

フックに matcher パターン("Write|Edit" など)がある場合、SDK はそれをイベントのターゲット(たとえば、ツール名)に対してテストします。マッチャーのないフックは、そのタイプのすべてのイベントに対して実行されます。

4

コールバック関数が実行される

各マッチングフックのコールバック関数は、何が起こっているかについての入力を受け取ります。ツール名、その引数、セッション ID、およびその他のイベント固有の詳細です。

5

コールバックが決定を返す

任意の操作(ログ、API 呼び出し、検証)を実行した後、コールバックは出力オブジェクトを返します。これはエージェントに何をするかを指示します。操作を許可する、ブロックする、入力を変更する、または会話にコンテキストを注入するなどです。

次の例は、これらのステップをまとめたものです。PreToolUse フック(ステップ 1)を "Write|Edit" マッチャー(ステップ 3)で登録して、コールバックがファイル書き込みツールに対してのみ発火するようにします。トリガーされると、コールバックはツールの入力(ステップ 4)を受け取り、ファイルパスが .env ファイルをターゲットにしているかどうかをチェックし、permissionDecision: "deny" を返して操作をブロックします(ステップ 5)。

import asyncio
from claude_agent_sdk import (
AssistantMessage,
ClaudeSDKClient,
ClaudeAgentOptions,
HookMatcher,
ResultMessage,
)


# ツール呼び出しの詳細を受け取るフックコールバックを定義する
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():
options = ClaudeAgentOptions(
hooks={
# PreToolUse イベントのフックを登録する
# マッチャーは Write と Edit ツール呼び出しのみにフィルタリングする
"PreToolUse": [HookMatcher(matcher="Write|Edit", hooks=[protect_env_files])]
}
)

async with ClaudeSDKClient(options=options) as client:
await client.query("Create a .env file with the standard local development database configuration")
async for message in client.receive_response():
# アシスタントとリザルトメッセージをフィルタリングする
if isinstance(message, (AssistantMessage, ResultMessage)):
print(message)


asyncio.run(main())

どちらのスクリプトを実行しても、Claude は .env ファイルを作成しようとし、フックはツール呼び出しを拒否し、Claude の最終的な応答は .env ファイルを作成できないことを説明します。

利用可能なフック

SDK はエージェント実行のさまざまなステージのフックを提供します。一部のフックは両方の SDK で利用可能ですが、その他は TypeScript のみです。

フックイベント Python SDK TypeScript SDK トリガーされる条件 使用例
PreToolUse はい はい ツール呼び出しリクエスト(ブロックまたは変更可能) 危険なシェルコマンドをブロックする
PostToolUse はい はい ツール実行結果 すべてのファイル変更を監査証跡にログする
PostToolUseFailure はい はい ツール実行失敗 ツールエラーを処理またはログする
PostToolBatch いいえ はい ツール呼び出しの完全なバッチが解決される。次のモデル呼び出しの前に 1 回 バッチ全体に対して規約を 1 回注入する
UserPromptSubmit はい はい ユーザープロンプト送信 プロンプトに追加のコンテキストを注入する
UserPromptExpansion いいえ はい ユーザーが入力したコマンド、または MCP プロンプトが Claude に到達する前にプロンプトに展開される。Claude がスキル自体を呼び出すときは発火しない コマンドの直接呼び出しをブロックするか、スキルが入力されたときにコンテキストを追加する
MessageDisplay いいえ はい テキスト付きのアシスタントメッセージが完了する。メッセージごとに 1 回、完全なメッセージテキスト付き 表示されたテキストを編集または再フォーマットする(トランスクリプトは変更しない)
Stop はい はい エージェント実行停止 終了前にセッション状態を保存する
StopFailure いいえ はい ターンが通常の停止ではなく API エラーで終了する 失敗をログするか、アラートを送信する
SubagentStart はい はい サブエージェント初期化 並列タスク生成を追跡する
SubagentStop はい はい サブエージェント完了 並列タスクから結果を集約する
PreCompact はい はい 会話圧縮リクエスト 要約する前に完全なトランスクリプトをアーカイブする
PostCompact いいえ はい 会話圧縮が完了する 生成されたサマリーをログする
PreModelSwitch いいえ はい リクエストされたモデルスイッチ。実行前(ブロック可能) 特定のモデルへの切り替えをブロックする
PostModelSwitch いいえ はい セッションのモデルが変更される。自動フォールバックを含む 新しいモデルに対して Claude モデル固有のガイダンスを提供する
PermissionRequest はい はい ツール呼び出しが権限決定を必要とする カスタム権限処理
PermissionDenied いいえ はい オートモードがツール呼び出しを拒否する。分類器の判定がない拒否を含む 拒否をログするか、モデルに再試行できることを伝える。Claude Code は判定なしの拒否に対して retry: true を無視する。PermissionDenied を参照
SessionStart いいえ はい セッション初期化 ログとテレメトリを初期化する
SessionEnd いいえ はい セッション終了 一時的なリソースをクリーンアップする
Notification はい はい エージェントステータスメッセージ エージェントステータス更新を Slack または PagerDuty に送信する
Setup いいえ はい セッション設定/メンテナンス 初期化タスクを実行する
TeammateIdle いいえ はい チームメイトがアイドル状態になる 作業を再割り当てするか通知する
TaskCreated いいえ はい TaskCreate ツール経由でタスクが作成される タスク命名規約を強制する
TaskCompleted いいえ はい タスクが完了としてマークされる タスクが閉じる前にテストに合格することを要求する
Elicitation いいえ はい MCP サーバーがタスク中にユーザー入力をリクエストする MCP 入力リクエストにプログラムで応答する
ElicitationResult いいえ はい ユーザーが MCP エリシテーションに応答する サーバーに返される前に応答を変更またはブロックする
ConfigChange いいえ はい 設定ファイル変更 設定を動的に再ロードする
InstructionsLoaded いいえ はい CLAUDE.md またはルールファイルがコンテキストにロードされる どの命令ファイルがロードされるかを監査する
WorktreeCreate いいえ はい Git ワークツリー作成 分離されたワークスペースを追跡する
WorktreeRemove いいえ はい Git ワークツリー削除 ワークスペースリソースをクリーンアップする
CwdChanged いいえ はい セッション中に作業ディレクトリが変更される ディレクトリごとに環境変数を再ロードする
FileChanged いいえ はい 監視対象ファイルが変更、作成、または削除される プロジェクトファイルが変更されたときに設定を再ロードする
DirectoryAdded いいえ はい セッション中に作業ディレクトリが追加される セッション中に追加されたリポジトリの依存関係をインストールする

フックを設定する

フックを設定するには、エージェントオプション(Python では ClaudeAgentOptions、TypeScript では options オブジェクト)の hooks フィールドに渡します。このスニペットは、上記の例から protect_env_files(Python)または protectEnvFiles(TypeScript)のようなフックコールバックを既に定義していることを前提としています。

options = ClaudeAgentOptions(
hooks={"PreToolUse": [HookMatcher(matcher="Bash", hooks=[my_callback])]}
)

async with ClaudeSDKClient(options=options) as client:
await client.query("Your prompt")
async for message in client.receive_response():
print(message)

hooks オプションは辞書(Python)またはオブジェクト(TypeScript)です。ここで:

マッチャー

マッチャーを使用して、コールバックがいつ発火するかをフィルタリングします。matcher フィールドは、フックイベントタイプに応じて異なる値に対してマッチングされます。たとえば、ツールベースのフックはツール名に対してマッチングされ、Notification フックは通知タイプに対してマッチングされます。

SDK マッチャーは設定ファイルのマッチャーと同じルールに従います。そのセクションでは、正確な文字列と正規表現の評価パス、バージョン要件、および各イベントタイプのマッチャー値を文書化しています。

オプション 型 デフォルト 説明
matcher string undefined イベントのフィルタフィールドに対してマッチングされるパターン。設定ファイルのマッチャーのルールに従います。ツールフックの場合、これはツール名です。組み込みツールには Bash、Read、Write、Edit、Glob、Grep、WebFetch、Agent などが含まれます(完全なリストについてはツール入力型を参照)。MCP ツールはパターン mcp__<server>__<action> を使用します。ここで <server> は mcpServers 設定で使用するキーです。
hooks HookCallback[] - 必須。パターンがマッチしたときに実行するコールバック関数の配列
timeout number undefined タイムアウト(秒単位)。省略した場合、Claude Code はイベントのデフォルトタイムアウトを適用します。SDK コールバックは command フックのデフォルトに従います

可能な限り matcher パターンを使用して特定のツールをターゲットにします。'Bash' のマッチャーは Bash コマンドに対してのみ実行されますが、パターンを省略するとコールバックはそのイベントのすべての発生に対して実行されます。セッションが行うすべてのツール呼び出しをログするために、意図的にパターンを省略します。

コールバック関数

入力

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

  • 入力データ: イベント詳細を含む型付きオブジェクト。各フック型には独自の入力形状があります。たとえば、PreToolUseHookInput には tool_name と tool_input が含まれ、NotificationHookInput には message が含まれます。TypeScript および Python SDK リファレンスで完全な型定義を参照してください。
    • すべてのフック入力は session_id、cwd、および hook_event_name を共有します。
    • agent_id と agent_type は、フックがサブエージェント内で発火するときに入力されます。TypeScript では、これらはベースフック入力にあり、すべてのフック型で利用可能です。Python では、これらは PreToolUse、PostToolUse、PostToolUseFailure、および PermissionRequest のオプションフィールドであり、SubagentStart および SubagentStop の必須フィールドです。
  • ツール使用 ID(str | None / string | undefined):同じツール呼び出しの PreToolUse と PostToolUse イベントを相関させます。
  • コンテキスト: TypeScript では、キャンセル用の signal プロパティ(AbortSignal)を含みます。Python では、この引数は将来の使用のために予約されています。

出力

コールバックは 2 つのカテゴリのフィールドを持つオブジェクトを返します。

  • トップレベルフィールドはすべてのイベントで受け入れられます。systemMessage はユーザーにメッセージを表示し、continue(Python では continue_)はこのフック後にエージェントが実行を続けるかどうかを決定します。一部のイベントはこれらを破棄するか、別の場所に配信します。各イベントのセクションはフックページでそれらがどこに着地するかを説明しています。
  • hookSpecificOutput は現在の操作を制御します。内部に設定するフィールドはフックイベントタイプに依存します。
    • PreToolUse フックの場合、ここで permissionDecision("allow"、"deny"、"ask"、または "defer")、permissionDecisionReason、および updatedInput を設定します。"defer" を返すとクエリが終了し、後で再開できます。
    • PostToolUse フックの場合、additionalContext を設定してツール結果に情報を追加できます。Claude がそれを見る前にツールの出力を置き換えるには、updatedToolOutput を設定します。これは両方の SDK のすべてのツールで機能します。古い updatedMCPToolOutput フィールドは MCP ツール出力のみを置き換え、非推奨です。
    • TypeScript SDK では、PostToolUse コールバックは classifierContext を返すこともできます。これはツール呼び出しの結果に関する短いメモで、自動モード権限分類器用です。コールバックはアプリケーション独自のプロセスで実行されるため、分類器はメモで中継するユーザーステートメントをユーザーの意図として重視する可能性があります。このフィールドは TypeScript Agent SDK v0.3.236 以降が必要です。自動モード分類器の結果に注釈を付けるは長さの上限、同期のみのルール、およびメモに何を入れないかをカバーしています。

変更なしで操作を許可するには {} を返します。SDK コールバックフックは、Claude Code シェルコマンドフックと同じ JSON 出力形式を使用します。これはすべてのフィールドとイベント固有のオプションを文書化しています。SDK 型定義については、TypeScript および Python SDK リファレンスを参照してください。

非同期出力

デフォルトでは、エージェントはフックが返されるのを待ってから続行します。フックがログやウェブフック送信などの副作用を実行し、エージェントの動作に影響を与える必要がない場合、代わりに非同期出力を返すことができます。これはエージェントに、フックが完了するのを待たずに即座に続行するよう指示します。このスニペットでは、Python の send_to_logging_service と TypeScript の sendToLoggingService は、定義する任意のログ関数の代わりです。

async def async_hook(input_data, tool_use_id, context):
# バックグラウンドタスクを開始してから即座に返す
asyncio.create_task(send_to_logging_service(input_data))
return {"async_": True, "asyncTimeout": 30000}
フィールド 型 説明
async true 非同期モードを通知します。エージェントは待たずに続行します。Python では、予約キーワードを避けるために async_ を使用します。
asyncTimeout number バックグラウンド操作のオプションのタイムアウト(ミリ秒単位)

例

このセクションのいくつかの例は、コールバック関数のみを示しています。実行するには、フックを設定するに示されているように、コールバックをオプションの hooks フィールドの対応するイベントの下に登録します。

ツール入力を変更する

この例は Write ツール呼び出しをインターセプトし、file_path 引数を書き直して /sandbox を先頭に追加し、すべてのファイル書き込みをサンドボックスディレクトリにリダイレクトします。コールバックは変更されたパスで updatedInput を返し、permissionDecision: 'allow' を返して書き直された操作を自動承認します。

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 {}

リダイレクトを確認するには、プレフィックスを書き込み可能なパス(./sandbox や /tmp/sandbox など)に設定します(macOS はルートレベルの /sandbox ディレクトリの作成を許可していません)。その後、エージェントにファイルを書き込むよう指示します。メッセージストリーム内の Write ツールの結果は、Claude が要求したパスではなく、サンドボックスプレフィックス付きのパスを示します。

コンテキストを追加してツールをブロックする

この例は /etc ディレクトリへの書き込みをブロックし、モデルとユーザーの両方に理由を説明します。

  • permissionDecision: 'deny' はツール呼び出しを停止します。
  • permissionDecisionReason はモデルに理由を伝えるため、再試行を避けます。
  • systemMessage はユーザーに何が起こったかを表示します。
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 {}

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

デフォルトでは、エージェントは特定のツールを使用する前にパーミッションを求めるプロンプトを表示する場合があります。この例は、permissionDecision: 'allow' を返すことで読み取り専用ファイルシステムツール(Read、Glob、Grep)を自動承認し、ユーザー確認なしで実行できるようにしながら、他のすべてのツールは通常のパーミッションチェックの対象のままにします。

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"]
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 {}

複数のフックを登録する

イベントが発火すると、すべてのマッチするフックが並列で実行されます。パーミッション決定の場合、最も制限的な結果が優先されます。単一の deny は、他のフックが何を返すかに関係なく、ツール呼び出しをブロックします。完了順序は非決定的であるため、別のフックが最初に実行されたことに依存するのではなく、各フックが独立して動作するように記述します。

以下の例は、すべてのツール呼び出しに対して 3 つの独立したチェックを登録します。

options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
HookMatcher(hooks=[authorization_check]),
HookMatcher(hooks=[input_validator]),
HookMatcher(hooks=[audit_logger]),
]
}
)

マルチツールマッチャーでフィルタリングする

マルチツールマッチャーを使用して、関連するツール間で 1 つのコールバックを共有します。この例は、異なるスコープを持つ 3 つのマッチャーを登録します。

  • パイプで区切られた正確なリスト(Write|Edit|NotebookEdit)は、ファイル変更ツールに対してのみ file_security_hook をトリガーします。
  • 正規表現(^mcp__)は、名前が mcp__ で始まる任意の MCP ツールに対して mcp_audit_hook をトリガーします。
  • 省略されたマッチャーは、名前に関係なくすべてのツール呼び出しに対して global_logger をトリガーします。
options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
# ファイル変更ツールをマッチングする
HookMatcher(matcher="Write|Edit|NotebookEdit", hooks=[file_security_hook]),
# すべての MCP ツールをマッチングする
HookMatcher(matcher="^mcp__", hooks=[mcp_audit_hook]),
# すべてをマッチングする(マッチャーなし)
HookMatcher(hooks=[global_logger]),
]
}
)

サブエージェントアクティビティを追跡する

SubagentStop フックを使用して、サブエージェントが作業を完了するときを監視します。TypeScript および Python SDK リファレンスで完全な入力型を参照してください。この例は、サブエージェントが完了するたびに概要をログします。

async def subagent_tracker(input_data, tool_use_id, context):
# サブエージェントが完了したときにサブエージェント詳細をログする
print(f"[SUBAGENT] Completed: {input_data['agent_id']}")
print(f"  Transcript: {input_data['agent_transcript_path']}")
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])]}
)

フックから HTTP リクエストを行う

フックは HTTP リクエストなどの非同期操作を実行できます。フック内でエラーをキャッチして、処理されない例外がエージェントを中断しないようにします。

この例は、各ツールが完了した後にウェブフックを送信し、どのツールが実行されたかと実行時刻をログします。フックはエラーをキャッチして、失敗したウェブフックがエージェントを中断しないようにします。

import asyncio
import json
import urllib.request
from datetime import datetime


def _send_webhook(tool_name):
"""外部ウェブフックにツール使用データを POST する同期ヘルパー。"""
data = json.dumps(
{
"tool": tool_name,
"timestamp": datetime.now().isoformat(),
}
).encode()
req = urllib.request.Request(
"https://api.example.com/webhook",
data=data,
headers={"Content-Type": "application/json"},
method="POST",
)
urllib.request.urlopen(req)


async def webhook_notifier(input_data, tool_use_id, context):
# ツールが完了した後(PostToolUse)に発火し、前ではない
if input_data["hook_event_name"] != "PostToolUse":
return {}

try:
# イベントループをブロックしないようにスレッドでブロッキング HTTP 呼び出しを実行する
await asyncio.to_thread(_send_webhook, input_data["tool_name"])
except Exception as e:
# エラーをログするが、発生させない。失敗したウェブフックはエージェントを停止すべきではない
print(f"Webhook request failed: {e}")

return {}

フックが発火することを確認するには、ウェブフック URL をウォッチできるエンドポイントに指定し、ツールを使用するプロンプトを送信します。フックは各ツール完了後にツール名とタイムスタンプを含む POST を送信します。

通知を Slack に転送する

Notification フックを使用して、エージェントからのシステム通知を受け取り、外部サービスに転送します。SDK セッションでは、Claude Code は以下の通知タイプに対してこのフックを実行します。

  • permission_prompt パーミッションリクエストが canUseTool コールバックで約 6 秒待機した後。TypeScript Agent SDK v0.3.233 以降、または Python Agent SDK v0.2.139 以降が必要です
  • ユーザープロンプト引き出しフロー用の elicitation_complete および elicitation_response

Claude Code は、SDK セッションが実行しない対話型 UI から idle_prompt、auth_success、および elicitation_dialog などの他のタイプを発行します。

各通知には、人間が読める説明を含む message フィールドと、オプションで title が含まれます。

この例は、すべての通知を Slack チャネルに転送します。Slack 受信ウェブフック URL が必要です。これは、Slack ワークスペースにアプリを追加し、受信ウェブフックを有効にすることで作成します。

import asyncio
import json
import urllib.request

from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, HookMatcher


def _send_slack_notification(message):
"""受信ウェブフック経由で Slack にメッセージを送信する同期ヘルパー。"""
data = json.dumps({"text": f"Agent status: {message}"}).encode()
req = urllib.request.Request(
"https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
data=data,
headers={"Content-Type": "application/json"},
method="POST",
)
urllib.request.urlopen(req)


async def notification_handler(input_data, tool_use_id, context):
try:
# イベントループをブロックしないようにスレッドでブロッキング HTTP 呼び出しを実行する
await asyncio.to_thread(_send_slack_notification, input_data.get("message", ""))
except Exception as e:
print(f"Failed to send notification: {e}")

# 空のオブジェクトを返す。通知フックはエージェント動作を変更しない
return {}


async def main():
options = ClaudeAgentOptions(
hooks={
# 通知イベントのフックを登録する(マッチャーは不要)
"Notification": [HookMatcher(hooks=[notification_handler])],
},
)

async with ClaudeSDKClient(options=options) as client:
await client.query("Analyze this codebase")
async for message in client.receive_response():
print(message)


asyncio.run(main())

Notification イベントが発火すると、フックは通知の message を Agent status: というプレフィックス付きでウェブフックが対象とするチャネルに投稿します。

一般的な問題を修正する

フックが発火しない

  • フックイベント名が正しく、大文字と小文字が区別されていることを確認します(preToolUse ではなく PreToolUse)
  • マッチャーパターンがツール名と正確にマッチしていることを確認します
  • フックが options.hooks の正しいイベントタイプの下にあることを確認します
  • Notification や SubagentStop などの非ツールフックでマッチャーをサポートする場合、マッチャーは異なるフィールドに対してマッチングされ、Stop はマッチャーを完全に無視します(マッチャーパターンを参照)
  • エージェントが max_turns 制限に達するとセッションが終了する前にフックが実行される可能性があるため、フックが発火しない場合があります

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

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

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

フックタイムアウト

Claude Code は各コールバックをタイムアウト付きで実行します。タイムアウトは HookMatcher の timeout フィールドで秒単位で設定します。設定しない場合、Claude Code はイベントのデフォルトを使用します:ほとんどのイベントで 600 秒、UserPromptSubmit、PreModelSwitch、PostModelSwitch で 30 秒、MessageDisplay で 10 秒です。Claude Code は SessionEnd コールバックをシャットダウン中に実行します。これはより短い SessionEnd タイムアウト予算の下で行われ、デフォルトは 1.5 秒です。

コールバックがタイムアウトを超過すると、Claude Code はそれをキャンセルし、失敗したフックとして扱います:コールバックの出力を破棄し、セッションはハングせずに続行されます。次に何が起こるかはイベントによって異なります:

  • PreToolUse:Claude Code はツール呼び出しを実行しません。Claude はフックがタイムアウト前に応答しなかったことを示すツール結果を受け取り、ターンは続行されます。別の PreToolUse フックが明示的な拒否を返した場合、Claude はタイムアウトエラーの代わりにその拒否を受け取ります。v2.1.210 より前では、Claude Code はタイムアウトを Claude にユーザー拒否として報告し、無人セッションが停止して入力を待つようになりました。
  • PostToolUse と PostToolUseFailure:Claude Code はツール結果を保持し、ターンは続行されます。
  • UserPromptSubmit と UserPromptExpansion:Claude Code はフック名とタイムアウトを示すメッセージでプロンプトをブロックし、セッションは続行されます。これらのイベントのコールバックはポリシーゲートとして機能できるため、Claude Code はタイムアウトしたプロンプトを未スクリーニングで通すことはありません。v2.1.208 より前では、これらのイベントのコールバックがタイムアウトすると Claude Code はクエリを error_during_execution で終了しました。
  • Stop と SubagentStop:Claude Code は警告を表示し、エージェントは正常に停止します。
  • PreModelSwitch:Claude Code はモデルスイッチをブロックします。応答しないフックはスイッチを承認していません。
  • Notification、PreCompact、PostModelSwitch などの他のイベント:Claude Code は失敗をログに記録し、続行されます。

コールバックが保留中の間にクエリを中断すると、Claude Code は保留中のツール呼び出しをキャンセルします。v2.1.208 より前では、保留中の PreToolUse コールバック中に中断した場合、ツール呼び出しは続行される可能性がありました。

コールバックがより多くの時間を必要とする場合は、HookMatcher で高い timeout を設定します。TypeScript では、3 番目のコールバック引数から AbortSignal を使用して、タイムアウトが発火したときにキャンセルを適切に処理します。

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

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

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

  • updatedInput が hookSpecificOutput の内部にあり、トップレベルにないことを確認します:

    return {
      hookSpecificOutput: {
        hookEventName: "PreToolUse",
        permissionDecision: "allow",
        updatedInput: { command: "new command" }
      }
    };
    
  • updatedInput を permissionDecision: 'defer' と組み合わせないでください。これは変更された入力を破棄します。permissionDecision を省略することは問題ありません:変更された入力は通常のパーミッション評価を通じて適用されます。変更された入力を自動承認するために 'allow' を返すか、ユーザーに承認を求めるために 'ask' を返すこともできます

  • hookSpecificOutput に hookEventName を含めて、出力がどのフック型用かを識別します

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

SessionStart と SessionEnd は TypeScript で SDK コールバックフックとして登録できますが、Python SDK では利用できません。その HookEvent 型はそれらを除外しています。Python では、.claude/settings.json などの設定ファイルで定義されたシェルコマンドフックとしてのみ利用可能です。SDK アプリケーションからシェルコマンドフックをロードするには、setting_sources または settingSources で適切な設定ソースを含めます:

options = ClaudeAgentOptions(
setting_sources=["project"],  # フックを含む .claude/settings.json をロード
)

Python SDK コールバックとして初期化ロジックを実行するには、client.receive_response() からの最初のメッセージをトリガーとして使用します。

サブエージェントパーミッションプロンプトが増加する

複数のサブエージェントを生成する場合、各サブエージェントは独自のツール呼び出しに対して個別にパーミッションをリクエストする可能性があります。繰り返されるプロンプトを避けるには、PreToolUse フックを使用して特定のツールを自動承認するか、サブエージェントが親会話から継承するパーミッションルールを設定します。

サブエージェントを使用した再帰的フックループ

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

  • サブエージェント指標をチェックしてからサブエージェントを生成する前にフック入力をチェックします
  • 共有変数またはセッション状態を使用して、既にサブエージェント内にいるかどうかを追跡します
  • フックをトップレベルエージェントセッションのみに実行するようにスコープします

systemMessage が出力に表示されない

systemMessage フィールドはユーザーにメッセージを表示します。Claude Code v2.1.227 以降では、フックの systemMessage はメッセージストリームに SDKInformationalMessage として表示される可能性があります。表示されるかどうかはイベントによって異なります。hooks ページの各イベントのセクションは、出力がどのように表示されるかを説明しています。代わりにモデルにコンテキストを渡すには、additionalContextを返します。

v2.1.227 より前では、SDK はメッセージストリームにフック出力を表示するのは SessionStart と Setup フックのみでした。他のイベントの場合、出力は includeHookEvents(Python では include_hook_events)が追加するライフサイクルイベントにのみ表示されました。そのオプションのエントリは、各フックイベントが生成するライフサイクルイベントをカバーしています。

フック決定をアプリケーションに確実に表示する必要がある場合は、別途ログするか、専用の出力チャネルを使用します。