
Hooks 是 Agent SDK 的「神经系统」——在工具调用前后、会话开始结束、用户提示提交等关键节点插入回调函数,不改动任何 SDK 内部代码,就能拦截危险操作、审计工具调用、动态注入上下文。本篇完整拆解生命周期三层结构(每会话/每轮/每工具)、HookMatcher 配置、五种决策输出、三个典型场景代码示例,以及 Python vs TypeScript 的关键差异。
ClaudeAgentOptions 上的回调函数,在 Agent 执行的特定节点被 SDK 自动调用。1matcher 过滤allow 放行、deny 拒绝、ask 要求用户确认、或直接修改工具的输入参数{} 等价于「不干预,继续」。SessionStart / SessionEnd:会话初始化与结束(仅 TypeScript SDK 支持回调形式)SubagentStart / SubagentStop:子 Agent 启动与完成UserPromptSubmit:用户提交 Prompt,Claude 处理之前Stop:Agent 本轮执行结束PreToolUse:工具调用前,可以拦截PostToolUse:工具成功后,可注入上下文PostToolUseFailure:工具失败后,可记录或给 Claude 反馈PostToolBatch:一批并行工具全部完成后,下一次模型调用前(仅 TypeScript)from claude_agent_sdk import ClaudeAgentOptions, HookMatcher
options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
HookMatcher(matcher="Write|Edit", hooks=[my_callback])
]
}
)HookMatcher 有三个字段:| 字段 | 类型 | 说明 |
|---|---|---|
matcher | str(正则) | 过滤工具名,如 "Write|Edit" 或 "^mcp__" |
hooks | list[HookCallback] | 必填,回调函数列表 |
timeout | int | 超时秒数,默认 60 |
matcher 只匹配工具名,不匹配文件路径。要按路径过滤,需要在回调内部检查 tool_input["file_path"]。async def my_callback(input_data: dict, tool_use_id: str | None, context) -> dict:
# input_data 包含 session_id、cwd、hook_event_name、tool_name、tool_input 等
# tool_use_id 用于关联同一工具调用的 PreToolUse 和 PostToolUse
# context 目前在 Python SDK 中预留,暂未使用
return {} # 返回空对象 = 不干预hookSpecificOutput 下。# 拒绝工具调用
return {
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "deny",
"permissionDecisionReason": "禁止写入 .env 文件",
}
}
# 自动放行
return {
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "allow",
}
}
# 修改工具的输入参数(同时自动放行)
return {
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "allow",
"updatedInput": {
**input_data["tool_input"],
"file_path": f"/sandbox{original_path}", # 重定向到沙箱目录
},
}
}permissionDecision 四个取值:"allow":跳过权限提示,直接执行"deny":阻止工具调用,把 permissionDecisionReason 返回给 Claude"ask":弹出用户确认对话框"defer":暂停执行,让调用方通过 --resume 手动处理(仅在 -p 非交互模式下有效)deny > defer > ask > allow——任意一个 deny 就足以拒绝。PostToolUse 里还有两个额外字段:additionalContext:在工具结果旁边向 Claude 注入补充信息updatedToolOutput:完全替换工具的输出(Claude 会看到替换后的版本,但工具已经跑完)async def protect_sensitive_files(input_data, tool_use_id, context):
file_path = input_data.get("tool_input", {}).get("file_path", "")
# 禁止修改 .env 和所有密钥文件
sensitive = [".env", ".env.local", "id_rsa", "credentials.json"]
if any(file_path.endswith(s) for s in sensitive):
return {
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"permissionDecision": "deny",
"permissionDecisionReason": f"文件 {file_path} 包含敏感信息,已拒绝修改",
}
}
return {}
options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
HookMatcher(matcher="Write|Edit", hooks=[protect_sensitive_files])
]
}
)import asyncio
from datetime import datetime
async def audit_file_changes(input_data, tool_use_id, context):
# 先返回异步信号,Agent 继续执行
asyncio.create_task(_write_audit(input_data))
return {"async_": True, "asyncTimeout": 5000}
async def _write_audit(input_data):
file_path = input_data.get("tool_input", {}).get("file_path", "unknown")
tool = input_data.get("tool_name", "unknown")
ts = datetime.now().isoformat()
with open("./audit.log", "a") as f:
f.write(f"{ts}\t{tool}\t{file_path}\n")
options = ClaudeAgentOptions(
hooks={
"PostToolUse": [
HookMatcher(matcher="Write|Edit", hooks=[audit_file_changes])
]
}
)deny 或 allow,只能做日志、指标、通知等副作用。async def inject_file_context(input_data, tool_use_id, context):
file_path = input_data.get("tool_input", {}).get("file_path", "")
# 如果修改的是自动生成文件,告知 Claude 不要直接改,要改源文件
if "generated" in file_path or file_path.endswith(".pb.go"):
return {
"hookSpecificOutput": {
"hookEventName": input_data["hook_event_name"],
"additionalContext": f"{file_path} 是自动生成文件,请修改对应的 .proto 源文件后重新生成",
}
}
return {}additionalContext 会被 Claude 在下一次模型调用时看到,相当于在工具结果旁贴了一张便利贴。| 特性 | Python SDK | TypeScript SDK |
|---|---|---|
SessionStart / SessionEnd 回调 | ❌ 不支持(只能用 settings.json shell hook) | ✅ 支持 |
PostToolBatch | ❌ 不支持 | ✅ 支持 |
async_ 字段(异步输出) | 用 async_ 避开 Python 保留字 | 用 async |
agent_id / agent_type 字段 | 仅在 PreToolUse / PostToolUse / PostToolUseFailure 可用 | 所有事件可用 |
context 参数 | 预留,暂不可用 | 包含 AbortSignal 用于取消 |
receive_response() 的第一条消息来代替,或在 .claude/settings.json 里配置 shell command hook 并设置 setting_sources=["project"]。1async def safe_hook(input_data, tool_use_id, context):
try:
result = await do_validation(input_data)
return result
except Exception as e:
# 记录日志,但不让异常传播
print(f"[hook error] {e}")
return {} # 降级为不干预additionalContext 写事实,不写指令AgentDefinition 定义专属 Agent、Agent 工具自动代理调用、父子 Agent 消息流的追踪字段,以及子 Agent 的权限隔离策略。docs.anthropic.com
docs.anthropic.com
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.