Was this page helpful?
フックを使用すると、エージェントの実行を主要なポイントでインターセプトし、バリデーション、ロギング、セキュリティ制御、またはカスタムロジックを追加できます。フックを使用すると、以下のことが可能です:
フックには2つの部分があります:
PreToolUseなど)、どのツールにマッチするかを指示する以下の例は、エージェントが.envファイルを変更するのをブロックします。まず、ファイルパスをチェックするコールバックを定義し、次にWriteまたはEditツールの呼び出し前に実行されるようにquery()に渡します:
これはPreToolUseフックです。ツールが実行される前に実行され、ロジックに基づいて操作をブロックまたは許可できます。このガイドの残りの部分では、利用可能なすべてのフック、その設定オプション、および一般的なユースケースのパターンについて説明します。
SDKは、エージェント実行のさまざまな段階に対応するフックを提供します。一部のフックは両方のSDKで利用可能ですが、Python SDKがサポートしていないため、TypeScriptのみのものもあります。
| フックイベント | Python SDK | TypeScript 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)で、以下の構造です:
フックコールバック関数はイベントに関する入力データを受け取り、エージェントが操作を許可、ブロック、または変更するかを知らせるレスポンスを返します。
マッチャーを使用して、どのツールがコールバックをトリガーするかをフィルタリングします:
| オプション | 型 | デフォルト | 説明 |
|---|---|---|---|
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 |
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を使用してキャンセルを適切に処理してください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出力モードで表示されるとは限りません。フックの決定をアプリケーションに表示する必要がある場合は、別途ログに記録するか、専用の出力チャネルを使用してください。
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に送信 |
| 必須。パターンがマッチしたときに実行するコールバック関数の配列 |
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 {}| Claudeが参照できるように会話に注入されるメッセージ |
object |
| PreToolUse |
変更されたツール入力(permissionDecision: 'allow'が必要) |
additionalContext | string | PreToolUse、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を含めてください