9.9 KiB
tags, create time
| tags | create time | |||||
|---|---|---|---|---|---|---|
|
2026-06-09 22:30 |
Hooks 生命周期钩子 - 执行引擎与拦截协议
概述
Claude Code 的 Hooks 系统定义了 27 种 Hook 事件,覆盖完整的 Agent 生命周期。通过 6 种 Hook 类型(command/prompt/agent/http/callback/function),Hooks 可以拦截工具调用、修改行为、注入上下文和控制执行流程,是企业级审计和安全加固的核心扩展点。
正文
27 种 Hook 事件
Claude Code 定义了 27 种 Hook 事件(HOOK_EVENTS 数组,src/entrypoints/sdk/coreTypes.ts),覆盖完整的 Agent 生命周期:
| 阶段 | 事件 | 触发时机 | 匹配字段 |
|---|---|---|---|
| 会话 | SessionStart |
会话启动 | source |
SessionEnd |
会话结束 | reason |
|
Setup |
初始化完成 | trigger |
|
| 用户交互 | UserPromptSubmit |
用户提交消息 | — |
Stop |
Agent 停止响应 | — | |
StopFailure |
Agent 停止失败 | error |
|
| 工具执行 | PreToolUse |
工具调用前 | tool_name |
PostToolUse |
工具调用后(成功) | tool_name |
|
PostToolUseFailure |
工具调用后(失败) | tool_name |
|
| 权限 | PermissionRequest |
权限请求 | tool_name |
PermissionDenied |
权限被拒 | tool_name |
|
| 子 Agent | SubagentStart |
子 Agent 启动 | agent_type |
SubagentStop |
子 Agent 停止 | agent_type |
|
| 压缩 | PreCompact |
上下文压缩前 | trigger |
PostCompact |
上下文压缩后 | trigger |
|
| 协作 | TeammateIdle |
Teammate 空闲 | — |
TaskCreated |
任务创建 | — | |
TaskCompleted |
任务完成 | — | |
| MCP | Elicitation |
MCP 服务器请求用户输入 | mcp_server_name |
ElicitationResult |
Elicitation 结果返回 | mcp_server_name |
|
| 通知 | Notification |
系统通知事件 | notification_type |
| 环境 | ConfigChange |
配置变更 | source |
CwdChanged |
工作目录变更 | — | |
FileChanged |
文件变更 | file_path |
|
InstructionsLoaded |
指令加载 | load_reason |
|
WorktreeCreate / WorktreeRemove |
Worktree 操作 | — |
6 种 Hook 类型
Hooks 配置支持 6 种执行方式,类型定义分布在 3 个文件中:
| 类型 | 执行方式 | 适用场景 | 定义位置 |
|---|---|---|---|
command |
Shell 命令(bash/PowerShell) | 通用脚本、CI 检查 | src/schemas/hooks.ts |
prompt |
注入到 AI 上下文 | 代码规范提醒 | src/schemas/hooks.ts |
agent |
启动子 Agent 执行 | 复杂分析任务 | src/schemas/hooks.ts |
http |
HTTP 请求 | 远程服务、Webhook | src/schemas/hooks.ts |
callback |
内部 JS 函数 | 系统内置 Hook | src/types/hooks.ts |
function |
运行时注册的函数 Hook | Agent/Skill 内部使用 | src/utils/hooks/sessionHooks.ts |
前四种(command/prompt/agent/http)为可持久化类型,通过 Zod schema 的 z.discriminatedUnion('type', [...]) 声明。
执行引擎:execCommandHook
execCommandHook()(src/utils/hooks.ts)是命令型 Hook 的执行核心:
flowchart TD
A["execCommandHook()"] --> B["Shell 选择\nhook.shell ?? DEFAULT_HOOK_SHELL"]
B --> C["变量替换\nCLAUDE_PLUGIN_ROOT, CLAUDE_PLUGIN_DATA, user_config.X"]
C --> D["环境变量注入\nCLAUDE_PROJECT_DIR, CLAUDE_ENV_FILE"]
D --> E["stdin 写入 jsonInput"]
E --> F["超时控制\nhook.timeout * 1000 ?? 600000ms"]
F --> G{"stdout 首行\n是否为 async: true?"}
G -->|"是"| H["转为后台任务\nAsyncHookRegistry"]
G -->|"否"| I["同步返回结果"]
异步 Hook 的检测协议
Hook 进程的 stdout 第一行如果是 {"async":true},系统将其转为后台任务(isAsyncHookJSONOutput 检测 + executeInBackground 调用)。
后台 Hook 通过 registerPendingAsyncHook() 注册到 AsyncHookRegistry,完成后通过 enqueuePendingNotification() 通知主线程。
asyncRewake:Hook 唤醒模型
asyncRewake 模式的 Hook 绕过 AsyncHookRegistry。当 Hook 退出码为 2 时,通过 enqueuePendingNotification() 以 task-notification 模式注入消息,唤醒空闲的模型(通过 useQueueProcessor)或在忙碌时注入 queued_command 附件。
Hook 输出的 JSON Schema
同步 Hook 的输出遵循严格的 Zod schema(syncHookResponseSchema,定义在 src/types/hooks.ts):
{
"continue": false,
"suppressOutput": true,
"stopReason": "安全检查失败",
"decision": "approve | block",
"reason": "原因说明",
"systemMessage": "警告内容",
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow | deny | ask",
"permissionDecisionReason": "匹配了安全规则",
"updatedInput": { },
"additionalContext": "额外上下文"
}
}
各事件的 hookSpecificOutput
| 事件 | 专有字段 | 作用 |
|---|---|---|
PreToolUse |
permissionDecision, permissionDecisionReason, updatedInput, additionalContext |
拦截/修改工具输入 |
PostToolUse |
additionalContext, updatedMCPToolOutput |
修改 MCP 工具输出 |
PostToolUseFailure |
additionalContext |
失败后注入上下文 |
UserPromptSubmit |
additionalContext |
注入额外上下文 |
SessionStart |
additionalContext, initialUserMessage, watchPaths |
设置初始消息和文件监控 |
PermissionRequest |
decision(含 allow/deny 子字段) |
权限请求的 Hook 决策 |
PermissionDenied |
retry |
指示是否重试 |
SubagentStart |
additionalContext |
子 Agent 启动时注入上下文 |
Elicitation |
action, content |
控制用户输入对话框 |
ElicitationResult |
action, content |
Elicitation 结果处理 |
Notification |
additionalContext |
通知事件注入上下文 |
Setup |
additionalContext |
初始化时注入上下文 |
CwdChanged |
watchPaths |
目录变更后更新监控路径 |
FileChanged |
watchPaths |
文件变更后更新监控路径 |
WorktreeCreate |
worktreePath |
Worktree 创建通知 |
Hook 匹配机制:getMatchingHooks
getMatchingHooks()(src/utils/hooks.ts)负责从所有来源中查找匹配的 Hook。
多来源合并
getHooksConfig()
├── getHooksConfigFromSnapshot() ← settings.json 中的 Hook(user/project/local)
├── getRegisteredHooks() ← SDK 注册的 callback Hook
├── getSessionHooks() ← Agent/Skill 前置注册的 session Hook
└── getSessionFunctionHooks() ← 运行时 function Hook
匹配规则
matcher 字段支持三种模式(matchesPattern() 函数):
"Write" → 精确匹配
"Write|Edit" → 管道分隔的多值匹配
"^Bash(git.*)" → 正则匹配
"*" 或 "" → 通配(匹配所有)
if 条件过滤
Hook 可以指定 if 条件,只在特定输入时触发。prepareIfConditionMatcher() 预编译匹配器:
{
"hooks": [{
"command": "check-git-branch.sh",
"if": "Bash(git push*)"
}]
}
if 条件使用 permissionRuleValueFromString 解析,支持与权限规则相同的语法(工具名 + 参数模式)。Bash 工具还会使用 tree-sitter 进行 AST 级别的命令解析。
Hook 去重
同一个 Hook 命令在不同配置层级(user/project/local)可能重复。系统按四部分复合键做 Map 去重:${pluginRoot}\0${shell}\0${command}\0${ifCondition}(由 hookDedupKey() 函数构建),保留最后合并的层级。
工作区信任检查
[!warning] 纵深防御 所有 Hook 都要求工作区信任(
shouldSkipHookDueToTrust()函数)。这是纵深防御措施——防止恶意仓库的.claude/settings.json在未信任的情况下执行任意命令。
SDK 非交互模式下信任是隐式的(getIsNonInteractiveSession() 为 true 时跳过检查)。
四种 Hook 能力的源码映射
1. 拦截操作(PreToolUse)
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny"
}
}
processHookJSONOutput() 将 permissionDecision 映射为 result.permissionBehavior = 'deny',并设置 blockingError,阻止工具执行。
2. 修改行为(updatedInput / updatedMCPToolOutput)
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"updatedInput": { "command": "npm test -- --bail" }
}
}
updatedInput 替换原始工具输入;updatedMCPToolOutput(PostToolUse 事件)替换 MCP 工具的返回值——可用于过滤敏感数据。
3. 注入上下文(additionalContext / systemMessage)
additionalContext-> 通过createAttachmentMessage({ type: 'hook_additional_context' })注入为用户消息systemMessage-> 注入为系统警告,直接显示给用户
4. 控制流程(continue / stopReason)
{ "continue": false, "stopReason": "构建失败,停止执行" }
continue: false 设置 preventContinuation = true,阻止 Agent 继续执行后续操作。
Session Hook 的生命周期
Agent 和 Skill 的前置 Hook 通过 registerFrontmatterHooks() 注册(调用位置:packages/builtin-tools/src/tools/AgentTool/runAgent.ts),绑定到 agent 的 session ID。Agent 结束时通过 clearSessionHooks()(src/utils/hooks/sessionHooks.ts)清理。
// runAgent.ts — 注册 agent 的前置 Hook
registerFrontmatterHooks(rootSetAppState, agentId, agentDefinition.hooks, ...)
// runAgent.ts — finally 块清理
clearSessionHooks(rootSetAppState, agentId)
[!tip] 隔离保证 这确保 Agent A 的 Hook 不会泄漏到 Agent B 的执行中。