フックを使用すると、エージェント実行の重要なポイントでインターセプトして、検証、ログ記録、セキュリティ制御、またはカスタムロジックを追加できます。フックを使用すると、以下のことができます:
フックには2つの部分があります:
PreToolUse)、どのツールをマッチするかを指示します次の例は、エージェントが.envファイルを変更するのをブロックします。まず、ファイルパスをチェックするコールバックを定義し、次にそれをquery()に渡して、Write または Edit ツール呼び出しの前に実行します:
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())これはPreToolUseフックです。ツール実行前に実行され、ロジックに基づいて操作をブロックまたは許可できます。このガイドの残りの部分では、利用可能なすべてのフック、その設定オプション、および一般的なユースケースのパターンについて説明します。
SDKは、エージェント実行のさまざまなステージのフックを提供します。一部のフックは両方のSDKで利用可能ですが、他のフックはTypeScript専用です。これはPython SDKがそれらをサポートしていないためです。
| フックイベント | Python SDK | TypeScript SDK | トリガーされるもの | 使用例 |
|---|---|---|---|---|
PreToolUse | はい | はい | ツール呼び出しリクエスト(ブロックまたは変更可能) | 危険なシェルコマンドをブロック |
PostToolUse | はい | はい | ツール実行結果 | すべてのファイル変更を監査証跡にログ |
PostToolUseFailure | いいえ | はい | ツール実行失敗 | ツールエラーを処理またはログ |
UserPromptSubmit | はい | はい | ユーザープロンプト送信 | プロンプトに追加コンテキストを注入 |
Stop |
フックは多くの異なるシナリオを処理するのに十分な柔軟性があります。以下は、カテゴリ別に整理された最も一般的なパターンの一部です。
エージェントのフックを設定するには、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)です。ここで:
フックコールバック関数は、イベントについての入力データを受け取り、レスポンスを返して、エージェントに操作を許可、ブロック、または変更するよう指示します。
マッチャーを使用して、どのツールがコールバックをトリガーするかをフィルタリングします:
| オプション | 型 | デフォルト | 説明 |
|---|---|---|---|
matcher | string | undefined | ツール名をマッチするための正規表現パターン。組み込みツールにはBash、Read、Write、Edit、Glob、Grep、WebFetch、Taskなどが含まれます。MCPツールはパターンmcp__<server>__<action>を使用します。 |
hooks | HookCallback[] | - | 必須。パターンがマッチしたときに実行するコールバック関数の配列 |
可能な限り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つの引数を受け取ります:
dict / HookInput):イベント詳細。フィールドについては入力データを参照してくださいstr | None / string | null):PreToolUseとPostToolUseイベントを相関させますHookContext):TypeScriptでは、キャンセル用のsignalプロパティ(AbortSignal)を含みます。fetch()などの非同期操作にこれを渡して、フックがタイムアウトした場合に自動的にキャンセルされるようにします。Pythonでは、この引数は将来の使用のために予約されています。フックコールバックの最初の引数には、イベントに関する情報が含まれています。フィールド名はSDK全体で同じです(両方ともsnake_caseを使用)。
すべてのフックタイプに存在する共通フィールド:
| フィールド | 型 | 説明 |
|---|---|---|
hook_event_name | string | フックタイプ(PreToolUse、PostToolUseなど) |
session_id | string | 現在のセッション識別子 |
transcript_path | string | 会話トランスクリプトへのパス |
cwd | string | 現在の作業ディレクトリ |
フック固有フィールドはフックタイプによって異なります。TSでマークされた項目はTypeScript SDKでのみ利用可能です:
| フィールド | 型 | 説明 | フック |
|---|---|---|---|
tool_name | string | 呼び出されるツールの名前 | PreToolUse、PostToolUse、PostToolUseFailureTS、PermissionRequestTS |
tool_input | object | ツールに渡される引数 | PreToolUse、PostToolUse、PostToolUseFailureTS、PermissionRequestTS |
tool_response | any | ツール実行から返された結果 | PostToolUse |
error | string | ツール実行失敗からのエラーメッセージ |
以下のコードは、tool_nameとtool_inputを使用して各ツール呼び出しの詳細をログするフックコールバックを定義しています:
コールバック関数は、SDKに続行方法を指示するオブジェクトを返します。操作を変更せずに許可するには、空のオブジェクト{}を返します。操作をブロック、変更、またはコンテキストを追加するには、hookSpecificOutputフィールドを含む決定を含むオブジェクトを返します。
トップレベルフィールド(hookSpecificOutputの外):
| フィールド | 型 | 説明 |
|---|---|---|
continue | boolean | このフック後にエージェントが続行するかどうか(デフォルト:true) |
stopReason | string | continueがfalseの場合に表示されるメッセージ |
suppressOutput | boolean | トランスクリプトからstdoutを非表示にする(デフォルト:false) |
systemMessage | string | Claudeが見るための会話に注入されるメッセージ |
hookSpecificOutput内のフィールド:
| フィールド | 型 | フック | 説明 |
|---|---|---|---|
hookEventName | string | すべて | 必須。現在のイベントをマッチするためにinput.hook_event_nameを使用します |
permissionDecision | 'allow' | 'deny' | 'ask' | PreToolUse | ツールが実行されるかどうかを制御します |
permissionDecisionReason | string | PreToolUse | 決定についてClaudeに表示される説明 |
updatedInput |
この例は/etcディレクトリへの書き込み操作をブロックしながら、Claudeに安全なファイルプラクティスについて思い出させるシステムメッセージを注入します:
複数のフックまたはパーミッションルールが適用される場合、SDKはこの順序で評価します:
フックが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()に渡して、フックがタイムアウトした場合にリクエストがキャンセルされるようにします:
Notificationフックを使用してエージェントからステータス更新を受け取り、Slackやモニタリングダッシュボードなどの外部サービスに転送します:
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値を増加させますAbortSignalを使用して、TypeScriptでキャンセルを適切に処理します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フィールドはモデルが見るための会話にコンテキストを追加しますが、すべてのSDK出力モードに表示されない場合があります。フック決定をアプリケーションに表示する必要がある場合は、別途ログするか、専用の出力チャネルを使用します。
| はい |
| はい |
| エージェント実行停止 |
| 終了前にセッション状態を保存 |
SubagentStart | いいえ | はい | サブエージェント初期化 | 並列タスク生成を追跡 |
SubagentStop | はい | はい | サブエージェント完了 | 並列タスクの結果を集約 |
PreCompact | はい | はい | 会話圧縮リクエスト | 要約前に完全なトランスクリプトをアーカイブ |
PermissionRequest | いいえ | はい | パーミッションダイアログが表示される | カスタムパーミッション処理 |
SessionStart | いいえ | はい | セッション初期化 | ログとテレメトリを初期化 |
SessionEnd | いいえ | はい | セッション終了 | 一時的なリソースをクリーンアップ |
Notification | いいえ | はい | エージェントステータスメッセージ | エージェントステータス更新をSlackまたはPagerDutyに送信 |
timeout | number | 60 | タイムアウト(秒単位)。外部APIを呼び出すフックの場合は増加させます |
| PostToolUseFailureTS |
is_interrupt | boolean | 失敗が割り込みによって引き起こされたかどうか | PostToolUseFailureTS |
prompt | string | ユーザーのプロンプトテキスト | UserPromptSubmit |
stop_hook_active | boolean | ストップフックが現在処理中かどうか | Stop、SubagentStop |
agent_id | string | サブエージェントの一意の識別子 | SubagentStartTS、SubagentStopTS |
agent_type | string | サブエージェントのタイプ/ロール | SubagentStartTS |
agent_transcript_path | string | サブエージェントの会話トランスクリプトへのパス | SubagentStopTS |
trigger | string | 圧縮をトリガーしたもの:manualまたはauto | PreCompact |
custom_instructions | string | 圧縮用に提供されたカスタム指示 | PreCompact |
permission_suggestions | array | ツールの推奨パーミッション更新 | PermissionRequestTS |
source | string | セッションの開始方法:startup、resume、clear、またはcompact | SessionStartTS |
reason | string | セッションが終了した理由:clear、logout、prompt_input_exit、bypass_permissions_disabled、またはother | SessionEndTS |
message | string | エージェントからのステータスメッセージ | NotificationTS |
notification_type | string | 通知のタイプ:permission_prompt、idle_prompt、auth_success、またはelicitation_dialog | NotificationTS |
title | string | エージェントによって設定されたオプションのタイトル | 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 {}object |
| PreToolUse |
変更されたツール入力(permissionDecision: 'allow'が必要) |
additionalContext | string | 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を含めます