- Published on
Claude Code Hook 系统深度解析:从源码看 AI 编程助手的可编程生命周期
- Authors
- Name
- 大聪明
- @wooluoo
引言
如果你用过 Claude Code,大概知道它支持在关键节点插入自定义脚本——比如在每次文件写入前跑 lint,在会话开始时自动加载环境变量,或者在 AI 即将输出时做最后一道检查。这些能力都由 Hook 系统 驱动。
但如果你看过 Claude Code 的源码,会发现这个 Hook 系统远比文档描述的要复杂和精妙。它不仅仅是"在某个时机跑个脚本"那么简单,而是一套完整的可编程生命周期框架,支持六种 Hook 类型、异步执行、LLM 驱动的智能 Hook、企业级权限控制,以及丰富的安全防护机制。
本文基于 Claude Code 最新源码,从架构设计到实现细节,全面拆解这套 Hook 系统。
一、Hook 的定义与六种类型
Claude Code 的 Hook 系统定义了六种执行方式(HookCommand 类型),每种适用于不同的场景:
1. Command Hook(命令型)
最基础的 Hook 类型,通过 bash 或 PowerShell 执行外部脚本。输入通过 stdin 以 JSON 传入,输出通过 stdout 返回。
// 配置示例
{
"type": "command",
"command": "npx eslint $ARGUMENTS",
"shell": "bash", // 可选,默认 bash
"timeout": 30, // 可选,超时秒数
"if": "Bash(git *)" // 可选,条件匹配
}
关键实现细节:
- Shell 选择:
hook.shell → DEFAULT_HOOK_SHELL,支持 bash 和 powershell 两种 shell - Windows 路径转换:bash hook 自动将 Windows 路径转为 POSIX 路径(
C:\Users→/c/Users),PowerShell 则跳过转换 - 环境变量注入:
CLAUDE_PROJECT_DIR、CLAUDE_PLUGIN_ROOT、CLAUDE_ENV_FILE等 .sh脚本自动处理:Windows 上自动为.sh文件添加bash前缀
2. Prompt Hook(提示型)
用 LLM(默认 Haiku)执行单轮验证。适合需要语义理解的场景,比如判断代码是否符合规范。
{
"type": "prompt",
"prompt": "检查 $ARGUMENTS 中的代码是否遵循项目规范。返回 {\"ok\": true/false, \"reason\": \"...\"}",
"model": "claude-haiku" // 可选,默认小快速模型
}
实现上,execPromptHook 会将 prompt 中的 $ARGUMENTS 替换为 JSON 输入,然后用 queryModelWithoutStreaming 进行单次 LLM 调用,要求返回结构化的 {ok, reason} 响应。
3. Agent Hook(代理型)
最强大的 Hook 类型,启动一个完整的 LLM Agent(最多 50 轮对话)来执行验证。Agent 可以使用所有可用工具来检查代码库。
{
"type": "agent",
"prompt": "验证以下修改是否完整实现了需求:$ARGUMENTS。使用工具检查代码库。"
}
execAgentHook 的实现非常精妙:
- 创建独立的
hookAgentId,确保 Agent 的 session hooks 不会泄漏到主 Agent - 过滤掉
ALL_AGENT_DISALLOWED_TOOLS(防止 Agent 嵌套 Agent) - 通过
SyntheticOutputTool强制结构化输出 - 注册
Stop事件上的 session hook 来强制 Agent 必须调用结构化输出工具 - 最多 50 轮对话,超时后 abort
4. HTTP Hook(网络型)
向外部服务发送 HTTP POST 请求,适合与企业系统集成。
{
"type": "http",
"url": "https://api.example.com/hook",
"headers": { "Authorization": "Bearer $MY_TOKEN" },
"allowedEnvVars": ["MY_TOKEN"],
"timeout": 60
}
安全特性:
- URL 白名单(
allowedHttpHookUrls) - 环境变量插值白名单(防止通过 Hook 泄露密钥)
- CRLF 注入防护(
sanitizeHeaderValue) - SSRF 防护(
ssrfGuardedLookup,阻止私有 IP 访问) - 沙箱代理支持(sandboxing 启用时路由请求通过代理)
5. Callback Hook(回调型)
内部使用的 TypeScript 回调函数,由 SDK 或插件注册,不暴露给用户。
6. Function Hook(函数型)
会话级内存回调,通过 addFunctionHook 注册,无法持久化到 settings.json。用于如结构化输出强制等运行时验证。
addFunctionHook(
setAppState,
sessionId,
'Stop',
'',
(messages, signal) => hasSuccessfulToolCall(messages, TOOL_NAME),
'You MUST call the verification tool',
{ timeout: 5000 }
)
二、Hook 的注册与发现机制
Claude Code 的 Hook 来源有五个层级,按优先级从高到低:
1. 设置文件(Settings)
三个层级的 settings.json:
userSettings:~/.claude/settings.jsonprojectSettings:.claude/settings.jsonlocalSettings:.claude/settings.local.json
在 hooksSettings.ts 的 getAllHooks 中遍历所有来源,并做文件去重(避免在 home 目录下 userSettings 和 projectSettings 指向同一文件)。
2. 注册 Hook(Registered Hooks)
通过 getRegisteredHooks() 获取,包括:
- SDK 回调(通过
registerHookCallbacksAPI) - 插件原生 Hook(
PluginHookMatcher,包含pluginRoot和pluginId)
3. 会话 Hook(Session Hooks)
存储在 AppState.sessionHooks 中(一个 Map<string, SessionStore>),包括:
- Skill frontmatter 注册的 Hook
- Agent frontmatter 注册的 Hook
- 运行时动态注册的 Function Hook
4. 快照机制
hooksConfigSnapshot.ts 实现了一个启动时快照模式:
captureHooksConfigSnapshot():启动时调用一次,冻结 Hook 配置getHooksConfigFromSnapshot():所有执行路径都从快照读取- 尊重
allowManagedHooksOnly和disableAllHooks策略
这意味着会话开始后修改 settings.json 不会影响当前会话的 Hook 配置。
5. 去重策略
getMatchingHooks 中的去重逻辑非常细致:
- 按 Hook 类型分别去重(command、prompt、agent、http)
- 去重 key 包含
pluginRoot/skillRoot,避免不同插件的模板碰撞 if条件不同的 Hook 视为不同 Hookshell类型是身份的一部分:{command:'echo x', shell:'bash'}和{command:'echo x', shell:'powershell'}是不同的 Hook
三、Hook 的执行生命周期
26 种事件类型
Claude Code 定义了令人惊叹的 26 种 Hook 事件(HOOK_EVENTS),覆盖了 AI 编程助手生命周期的方方面面:
工具生命周期:
PreToolUse:工具执行前(支持permissionDecision、updatedInput)PostToolUse:工具执行后(支持additionalContext、updatedMCPToolOutput)PostToolUseFailure:工具执行失败后
权限系统:
PermissionDenied:自动模式拒绝工具调用时(支持retry)PermissionRequest:权限对话框显示时(支持decision: {behavior, updatedInput})
会话生命周期:
SessionStart:新会话开始(支持initialUserMessage、watchPaths)SessionEnd:会话结束Stop:AI 即将结束回复StopFailure:API 错误导致回复结束UserPromptSubmit:用户提交 prompt 时
Agent 系统:
SubagentStart/SubagentStop:子 Agent 开始/结束TeammateIdle:队友即将空闲TaskCreated/TaskCompleted:任务创建/完成
系统事件:
Notification:通知发送时Setup:仓库初始化/维护PreCompact/PostCompact:对话压缩前后ConfigChange:配置文件变更InstructionsLoaded:指令文件加载WorktreeCreate/WorktreeRemove:工作树管理CwdChanged/FileChanged:目录/文件变更Elicitation/ElicitationResult:MCP 用户输入请求
执行流程
核心执行函数 executeHooks 的流程:
1. 检查 disableAllHooks / CLAUDE_CODE_SIMPLE
2. 检查 workspace trust(安全关键)
3. 获取匹配的 Hook(getMatchingHooks)
4. 分离 internal hooks(快速路径)vs user hooks
5. 并行执行所有 Hook(Promise.all)
6. 聚合结果:权限决策(deny > ask > allow 优先级)
对于纯内部回调 Hook(如 sessionFileAccessHooks),有一个极致优化:跳过 span/progress/abortSignal 等所有开销,直接批量执行回调。测量结果显示从 6.01µs 降到 ~1.8µs(-70%)。
异步 Hook 支持
Command Hook 支持异步模式,两种方式:
- 配置声明:
hook.async = true - 协议检测:stdout 第一行输出
{"async": true, ...}
异步 Hook 通过 AsyncHookRegistry 管理:
- 注册到
pendingHooksMap - 进度追踪(
startHookProgressInterval,每秒轮询 stdout/stderr) - 完成时通过
checkForAsyncHookResponses检查结果 asyncRewake模式:Hook 完成后如果 exit code 2,通过enqueuePendingNotification唤醒 AI
四、退出码协议与输出解析
Command Hook 的退出码有一套精心设计的语义:
| 退出码 | 含义 | 影响 |
|---|---|---|
| 0 | 成功 | stdout 可注入到 AI 上下文 |
| 2 | 阻塞错误 | stderr 发送给 AI,阻止操作继续 |
| 其他 | 非阻塞错误 | stderr 仅显示给用户 |
对于 JSON 输出,解析流程更复杂:
// 顶层字段
{
"continue": false, // 阻止继续
"stopReason": "...", // 停止原因
"decision": "approve/block", // 权限决策
"systemMessage": "...", // 系统消息
"suppressOutput": true, // 抑制输出
"reason": "...", // 原因说明
"hookSpecificOutput": { // 事件特定输出
"hookEventName": "PreToolUse",
"permissionDecision": "allow/deny/ask",
"updatedInput": {...}, // 修改工具输入
"additionalContext": "..." // 额外上下文
}
}
值得注意的是,hookSpecificOutput.hookEventName 必须与实际事件匹配,否则抛出错误。这是防止 Hook 输出被错误应用到错误事件的防护。
五、安全模型
Workspace Trust
所有 Hook 都要求 workspace trust。这是一个集中式的安全检查:
export function shouldSkipHookDueToTrust(): boolean {
const isInteractive = !getIsNonInteractiveSession()
if (!isInteractive) return false // SDK 模式隐式信任
return !checkHasTrustDialogAccepted()
}
这个设计源于历史漏洞:SessionEnd Hook 在用户拒绝信任对话框时仍会执行。
企业策略控制
hooksConfigSnapshot.ts 实现了三层策略:
disableAllHooks(managed):禁用所有 Hook,包括 managedallowManagedHooksOnly:仅允许 managed settings 中的 HookdisableAllHooks(non-managed):仅禁用非 managed Hook(managed Hook 仍然运行)
HTTP Hook 额外有:
allowedHttpHookUrls:URL 白名单httpHookAllowedEnvVars:环境变量白名单
SSRF 防护
execHttpHook.ts 中的 ssrfGuardedLookup 阻止向私有 IP 发起请求(但允许 loopback 用于本地开发)。当使用代理(环境变量代理或沙箱代理)时自动跳过 SSRF 检查。
条件匹配安全
Hook 的 if 条件(如 "Bash(git *)")通过 prepareIfConditionMatcher 预编译,复用权限系统的 permissionRuleValueFromString 解析器和工具的 preparePermissionMatcher 方法,确保匹配逻辑与权限系统一致。
六、内置 Hook 示例
结构化输出强制
registerStructuredOutputEnforcement 是一个典型的内置 Function Hook:
export function registerStructuredOutputEnforcement(setAppState, sessionId) {
addFunctionHook(
setAppState,
sessionId,
'Stop',
'',
(messages) => hasSuccessfulToolCall(messages, SYNTHETIC_OUTPUT_TOOL_NAME),
`You MUST call the ${SYNTHETIC_OUTPUT_TOOL_NAME} tool to complete this request.`,
{ timeout: 5000 }
)
}
当 Agent Hook 的 Agent 即将结束回复但没有调用结构化输出工具时,这个 Hook 会阻止结束并提醒 Agent。
Skill/Agent Frontmatter Hook
registerSkillHooks 和 registerFrontmatterHooks 将 frontmatter 中定义的 Hook 注册为会话级 Hook:
# 在 skill/agent 的 frontmatter 中
hooks:
Stop:
- matcher: ''
hooks:
- type: command
command: './verify.sh'
once: true # 执行一次后自动移除
once: true 的 Hook 通过 onHookSuccess 回调在首次成功执行后自动移除。
文件变更监视
fileChangedWatcher.ts + CwdChanged / FileChanged Hook 构成了一个文件监视系统:
CwdChangedHook 可以通过hookSpecificOutput.watchPaths注册监视路径FileChangedHook 在文件变更时触发,也可以动态更新监视列表CLAUDE_ENV_FILE环境变量允许 Hook 写入环境变量定义,后续 Bash 工具命令会自动加载
七、事件广播系统
hookEvents.ts 实现了一个独立于主消息流的 Hook 事件广播系统:
type HookExecutionEvent =
| { type: 'started'; hookId; hookName; hookEvent }
| { type: 'progress'; hookId; hookName; hookEvent; stdout; stderr; output }
| { type: 'response'; hookId; hookName; hookEvent; output; stdout; stderr; exitCode; outcome }
SessionStart和Setup事件始终广播(低噪音)- 其他事件仅在
allHookEventsEnabled时广播(SDKincludeHookEvents选项) - 支持事件缓冲(最多 100 个),handler 注册后立即投递积压事件
八、CLAUDE_CODE_SIMPLE 模式
通过环境变量 CLAUDE_CODE_SIMPLE=1 可以完全禁用所有 Hook 执行。这在 executeHooks 和 executeHooksOutsideREPL 中都有检查。这个开关为简化模式(如 CI 环境)提供了快速关闭所有 Hook 扩展点的能力。
总结
Claude Code 的 Hook 系统是一个设计精良的可编程生命周期框架。它的核心设计哲学是:
- 安全优先:workspace trust、策略分层、SSRF 防护、环境变量白名单
- 渐进增强:从简单的 bash 脚本到完整的 LLM Agent,六种 Hook 类型覆盖不同复杂度需求
- 事件驱动:26 种精细事件让开发者可以在 AI 编程助手的任何关键节点插入自定义逻辑
- 企业友好:managed settings 策略、URL 白名单、配置变更审计
- 性能敏感:内部 Hook 快速路径、快照机制、条件预编译
这套系统让 Claude Code 不仅仅是一个 AI 编程助手,更像是一个可编程的 AI 操作系统内核——Hook 就是它的可加载内核模块。
本文基于 Claude Code 源码分析,涉及的源码文件主要包括:src/utils/hooks.ts(核心执行引擎)、src/utils/hooks/(各子模块)、src/utils/hooks/hooksConfigManager.ts(配置管理)、src/utils/hooks/hooksSettings.ts(设置读取)。