SpyBara
Go Premium

agent-sdk/hooks.md 2026-10-09 23:02 UTC to 2026-10-10 21:01 UTC

This page contains 30 additions and 30 deletions.

2026
Thu 1 23:59 Fri 2 22:59 Sat 3 23:57 Wed 7 23:59 Sat 10 22:01

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

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

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

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

フックの仕組み

1

イベントが発火する

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

2

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

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

3

matcher がどのフックを実行するかをフィルタリングする

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

4

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

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

5

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

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

次の例は、これらのステップをまとめたものです。PreToolUse フック(ステップ 1)を "Write|Edit" matcher(ステップ 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 イベントのフックを登録する
# matcher は 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 ファイルを作成しようとし、フックはツール呼び出しを拒否します。

利用可能なフック

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

フックイベント Python SDK TypeScript SDK トリガーされる条件 使用例
PreToolUse はい はい ツール呼び出しリクエスト(ブロックまたは変更可能) 危険なシェルコマンドをブロックする
PostToolUse はい はい ツール実行結果 すべてのファイル変更を監査証跡にログする
PostToolUseFailure はい はい ツール実行失敗 ツールエラーを処理またはログする
PostToolBatch いいえ はい ツール呼び出しの完全なバッチが解決される。次のモデル呼び出しの前に 1 回 バッチ全体に対して規約を 1 回注入する
UserPromptSubmit はい はい プロンプトが送信される。Claude Code が自ら開始するターンを含む プロンプトに追加のコンテキストを注入する
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 いいえ はい WorktreeCreate フックによって作成された worktree が削除されようとしている ワークスペースリソースをクリーンアップする
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" を返すと、stop_reason が "tool_deferred" である結果メッセージでターンが終了するため、後で呼び出しを再開できます。
    • PostToolUse フックの場合、additionalContext を設定してツール結果に情報を追加できます。Claude がそれを見る前にツールの出力を置き換えるには、updatedToolOutput を設定します。これは両方の SDK のすべてのツールで機能します。古い updatedMCPToolOutput フィールドは MCP ツール出力のみを置き換えます。
    • TypeScript SDK では、PostToolUse コールバックは classifierContext を返すこともできます。これはツール呼び出しの結果に関する短いメモで、auto モードの権限分類器用です。コールバックはアプリケーション独自のプロセスで実行されるため、分類器はメモで中継するユーザーステートメントをユーザーの意図として重視する可能性があります。このフィールドは TypeScript Agent SDK v0.3.236 以降が必要です。auto モード分類器の結果に注釈を付けるは長さの上限、同期のみのルール、およびメモに何を入れないかをカバーしています。

変更なしで操作を許可するには {} を返します。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 {
# Top-level field: message shown to the user
"systemMessage": "Remember: system directories like /etc are protected.",
# hookSpecificOutput: block the operation
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "deny",
"permissionDecisionReason": "Writing to /etc is not allowed",
},
}
return {}

ブロックが機能していることを確認するには、Write|Edit matcher を指定して PreToolUse の下にコールバックを登録し、エージェントに /etc 配下でのファイル作成を依頼します。メッセージストリーム内の Write ツールの結果に Writing to /etc is not allowed が含まれ、ファイルは作成されません。

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

デフォルトでは、エージェントは特定のツールを使用する前に権限を求めることがあります。この例では、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 {}

複数のフックを登録する

イベントが発生すると、一致するすべてのフックが並列に実行されます。権限の決定については、最も制限の厳しい結果が適用されます。1 つでも deny があれば、他のフックが何を返したかに関係なくツール呼び出しはブロックされます。完了順序は非決定的であるため、別のフックが先に実行されていることに依存せず、各フックが独立して動作するように記述してください。

以下の例では、すべてのツール呼び出しに対して 3 つの独立したチェックを登録します。例中のフック名(Python の audit_logger や TypeScript の auditLogger など)は、ユーザーが定義するコールバックを表しています。

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

複数ツールの matcher でフィルタリングする

複数ツールの matcher を使用すると、関連するツール間で 1 つのコールバックを共有できます。この例ではスコープの異なる 3 つの matcher を登録しており、例中の各フックはユーザーが定義するコールバックを表しています。

  • パイプ区切りの完全一致リスト(Write|Edit|NotebookEdit)は、ファイル変更ツールに対してのみ file_security_hook をトリガーします。
  • 正規表現(^mcp__)は、名前が mcp__ で始まるすべての MCP ツールに対して mcp_audit_hook をトリガーします。
  • matcher を省略すると、名前に関係なくすべてのツール呼び出しに対して global_logger をトリガーします。
options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
# Match file modification tools
HookMatcher(matcher="Write|Edit|NotebookEdit", hooks=[file_security_hook]),
# Match all MCP tools
HookMatcher(matcher="^mcp__", hooks=[mcp_audit_hook]),
# Match everything (no matcher)
HookMatcher(hooks=[global_logger]),
]
}
)

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

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

async def subagent_tracker(input_data, tool_use_id, context):
# Log subagent details when it finishes
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])]}
)

フックが発火することを確認するには、コールバックを登録し、現在のディレクトリ内のファイル一覧の取得など、小さなタスクをサブエージェントに委任するようエージェントに依頼します。サブエージェントが完了すると、コールバックがサブエージェントの ID とトランスクリプトのパスを含む [SUBAGENT] Completed: の行を出力します。

フックから HTTP リクエストを送信する

フックは HTTP リクエストなどの非同期操作を実行できます。エラーは伝播させず、フック内でキャッチしてください。

この例では、各ツールの完了後に Webhook を送信し、どのツールがいつ実行されたかを記録します。フックは Webhook の失敗によるエラーをキャッチします。

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


def _send_webhook(tool_name):
"""Synchronous helper that POSTs tool usage data to an external webhook."""
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):
# Only fire after a tool completes (PostToolUse), not before
if input_data["hook_event_name"] != "PostToolUse":
return {}

try:
# Run the blocking HTTP call in a thread to avoid blocking the event loop
await asyncio.to_thread(_send_webhook, input_data["tool_name"])
except Exception as e:
# Log the error but don't raise
print(f"Webhook request failed: {e}")

return {}

フックが発火することを確認するには、Webhook の 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:ユーザーへの入力要求(elicitation)フロー用

idle_prompt、auth_success、elicitation_dialog などのその他のタイプは、SDK セッションでは実行されないインタラクティブ UI から Claude Code が発行します。

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

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

import asyncio
import json
import urllib.request

from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, HookMatcher


def _send_slack_notification(message):
"""Synchronous helper that sends a message to Slack via incoming webhook."""
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:
# Run the blocking HTTP call in a thread to avoid blocking the event loop
await asyncio.to_thread(_send_slack_notification, input_data.get("message", ""))
except Exception as e:
print(f"Failed to send notification: {e}")

# Return empty object. Notification hooks don't modify agent behavior
return {}


async def main():
options = ClaudeAgentOptions(
hooks={
# Register the hook for Notification events (no matcher needed)
"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: を付けて、Webhook の送信先チャンネルに投稿します。

一般的な問題を修正する

フックが発火しない

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

matcher が期待通りにフィルタリングしない

matcher はツール名のみをマッチし、ファイルパスや他の引数はマッチしません。ファイルパスでフィルタリングするには、フック内で 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 {}; // Skip non-markdown files
  // Process markdown files...
  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 v2.1.273 より前では、タイムアウトした Stop または SubagentStop コールバックは失敗したフック実行としてカウントされ、Claude Code はイベントの他のフックの決定を破棄していました。
  • SessionStart:タイムアウトしたコールバックは出力を返さないとカウントされ、セッションは他の SessionStart フックの出力で続行されます。
  • PreModelSwitch:Claude Code はモデルスイッチをブロックします。応答しないフックはスイッチを承認していません。
  • Notification、PreCompact、PostModelSwitch などの他のイベント:Claude Code は失敗をログに記録して続行します。

メインセッションで Stop または SessionStart コールバックが初めてタイムアウトした場合、Claude Code はメッセージストリームに SDKInformationalMessage も追加します。これはセッションを駆動するアプリが応答しなかったことを示します。その後のタイムアウトはアプリが応答しない間、そのメッセージを繰り返しません。

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

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

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

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

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

  • 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 コールバックフックとして登録できますが、その HookEvent タイプがそれらを省略しているため、Python SDK では利用できません。Python では、.claude/settings.json などの設定ファイルで定義された シェルコマンドフックとしてのみ利用できます。SDK アプリケーションがどの設定ファイルをロードするかは、setting_sources または settingSources によって決まります。このオプションを設定する場合は、フックを含むソースを含めてください:

options = ClaudeAgentOptions(
setting_sources=["project"],  # Loads .claude/settings.json including hooks
)

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

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

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

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

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

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

systemMessage が出力に表示されない

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

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

フック決定をアプリケーションに確実に表示する必要がある場合は、それらを個別にログに記録するか、専用の出力チャネルを使用してください。