钩子让您可以在关键点拦截代理执行,以添加验证、日志记录、安全控制或自定义逻辑。通过钩子,您可以:
钩子由两部分组成:
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 | 是 | 是 | 工具调用请求(可以阻止或修改) | 阻止危险的 shell 命令 |
PostToolUse | 是 | 是 | 工具执行结果 | 将所有文件更改记录到审计跟踪 |
PostToolUseFailure | 否 | 是 | 工具执行失败 | 处理或记录工具错误 |
UserPromptSubmit | 是 | 是 | 用户提示提交 | 向提示中注入额外上下文 |
Stop | 是 | 是 | 代理执行停止 | 退出前保存会话状态 |
SubagentStart | 否 | 是 | 子代理初始化 | 跟踪并行任务生成 |
SubagentStop | 是 | 是 | 子代理完成 | 聚合并行任务的结果 |
PreCompact | 是 | 是 | 对话压缩请求 | 在摘要之前归档完整记录 |
PermissionRequest | 否 | 是 | 将显示权限对话框 | 自定义权限处理 |
SessionStart | 否 | 是 | 会话初始化 | 初始化日志记录和遥测 |
SessionEnd | 否 | 是 | 会话终止 | 清理临时资源 |
Notification | 否 | 是 | 代理状态消息 | 将代理状态更新发送到 Slack 或 PagerDuty |
钩子足够灵活,可以处理许多不同的场景。以下是按类别组织的一些最常见模式。
要为您的代理配置钩子,请在调用 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[] | - | 必需。当模式匹配时要执行的回调函数数组 |
timeout | number | 60 | 超时时间(秒);对于进行外部 API 调用的钩子,请增加此值 |
尽可能使用 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])
]
}
)每个钩子回调接收三个参数:
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 | 工具执行失败的错误消息 | 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 |
以下代码定义了一个钩子回调,使用 tool_name 和 tool_input 来记录每个工具调用的详情:
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 {}您的回调函数返回一个对象,告诉 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 | object | PreToolUse | 修改后的工具输入(需要 permissionDecision: 'allow') |
additionalContext | string | PreToolUse、PostToolUse、UserPromptSubmit、SessionStartTS、SubagentStartTS | 添加到对话中的上下文 |
此示例阻止对 /etc 目录的写入操作,同时注入系统消息以提醒 Claude 安全的文件操作实践:
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 {}当多个钩子或权限规则适用时,SDK 按以下顺序评估它们:
如果任何钩子返回 deny,操作将被阻止——其他钩子返回 allow 不会覆盖它。
返回拒绝决定以阻止工具执行:
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 {}使用 updatedInput 时,您还必须包含 permissionDecision。始终返回一个新对象,而不是修改原始的 tool_input。
向对话中注入上下文:
async def add_security_reminder(input_data, tool_use_id, context):
return {
'systemMessage': 'Remember to follow security best practices.'
}绕过受信任工具的权限提示。当您希望某些操作无需用户确认即可运行时,这很有用:
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 {}permissionDecision 字段接受三个值:'allow'(自动批准)、'deny'(阻止)或 'ask'(提示确认)。
这些模式帮助您为复杂用例构建更复杂的钩子系统。
钩子按照它们在数组中出现的顺序执行。保持每个钩子专注于单一职责,并链接多个钩子以实现复杂逻辑。此示例对每个工具调用运行所有四个钩子(未指定匹配器):
options = ClaudeAgentOptions(
hooks={
'PreToolUse': [
HookMatcher(hooks=[rate_limiter]), # 第一:检查速率限制
HookMatcher(hooks=[authorization_check]), # 第二:验证权限
HookMatcher(hooks=[input_sanitizer]), # 第三:清理输入
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])
]
}
)匹配器仅匹配工具名称,而不是文件路径或其他参数。要按文件路径过滤,请在钩子回调中检查 tool_input.file_path。
使用 SubagentStop 钩子监控子代理完成情况。tool_use_id 帮助将父代理调用与其子代理关联起来:
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])]
}
)钩子可以执行异步操作,如 HTTP 请求。通过捕获异常而不是抛出异常来优雅地处理错误。在 TypeScript 中,将 signal 传递给 fetch() 以便在钩子超时时取消请求:
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 {}使用 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 {}; // 跳过非 markdown 文件
// 处理 markdown 文件...
};HookMatcher 配置中的 timeout 值AbortSignal 来优雅地处理取消PreToolUse 钩子中返回 permissionDecision: 'deny' 的情况permissionDecisionReason确保 updatedInput 在 hookSpecificOutput 内部,而不是在顶层:
return {
hookSpecificOutput: {
hookEventName: input.hook_event_name,
permissionDecision: 'allow',
updatedInput: { command: 'new command' }
}
};您还必须返回 permissionDecision: 'allow' 才能使输入修改生效
在 hookSpecificOutput 中包含 hookEventName 以标识输出对应的钩子类型
SessionStart、SessionEnd 和 Notification 钩子仅在 TypeScript SDK 中可用。由于设置限制,Python SDK 不支持这些事件。
当生成多个子代理时,每个子代理可能会分别请求权限。子代理不会自动继承父代理的权限。为避免重复提示,请使用 PreToolUse 钩子自动批准特定工具,或配置适用于子代理会话的权限规则。
生成子代理的 UserPromptSubmit 钩子如果这些子代理触发相同的钩子,可能会创建无限循环。要防止这种情况:
parent_tool_use_id 字段检测您是否已在子代理上下文中systemMessage 字段向对话中添加模型可以看到的上下文,但它可能不会出现在所有 SDK 输出模式中。如果您需要将钩子决策呈现给您的应用程序,请单独记录它们或使用专用的输出通道。
Was this page helpful?