Chapter 07
Hook 系统
Hook 系统让用户可以在 Claude Code 的生命周期关键节点注入自定义行为 — 从工具执行前后到会话开始结束。
Hook 系统概览
Hook 是用户配置的 shell 命令,在特定事件触发时自动执行。每个 Hook 接收 JSON 格式的输入(通过 stdin),通过 exit code 和 stdout/stderr 影响系统行为。
核心文件:
src/utils/hooks/ 目录下包含:AsyncHookRegistry.ts(异步注册表)、hookEvents.ts(事件系统)、hooksConfigManager.ts(配置管理)、execPromptHook.ts(执行引擎)。
15+ 生命周期事件
| 事件名 | 触发时机 | 输入 (stdin JSON) | 可控行为 |
|---|---|---|---|
| 核心生命周期 | |||
| SessionStart | 会话开始 (init/resume/clear/compact) | session 信息 | 初始化自定义环境 |
| Setup | 仓库初始化/维护 | repo 信息 | 执行 setup 脚本 |
| UserPromptSubmit | 用户输入处理前 | 用户提示内容 | 拦截/替换用户输入 |
| Stop | 助手即将结束回复 | 回复信息 | exit 2 → stderr 显示给模型 |
| 工具钩子 | |||
| PreToolUse | 工具执行前 | 工具名 + 输入参数 JSON | exit 0=静默, 2=阻止+原因 |
| PostToolUse | 工具执行后 | {inputs, response} | exit 0=transcript, 2=显示给模型 |
| PostToolUseFailure | 工具执行失败 | {tool_name, error, is_timeout} | 错误日志/通知 |
| PermissionRequest | 权限对话框显示时 | {tool_name, tool_input} | Hook 决定 allow/deny |
| PermissionDenied | auto 模式分类器拒绝 | {tool_name, reason} | 设置 {retry: true} 重试 |
| Agent 钩子 | |||
| SubagentStart | 子 Agent 生成时 | {agent_id, agent_type} | 记录/通知 |
| SubagentStop | 子 Agent 结束时 | {agent_id, transcript_path} | 清理/分析 |
| 压缩钩子 | |||
| PreCompact | 上下文压缩前 | {trigger: 'manual'|'auto'} | exit 0=追加指令, 2=阻止压缩 |
| PostCompact | 上下文压缩后 | {trigger} | exit 0=stdout 显示给用户 |
| 其他钩子 | |||
| Notification | 通知发送时 | {notification_type} | 自定义通知行为 |
| TaskCreated/Completed | 任务生命周期 | {task_id, task_subject} | exit 2=阻止 |
配置方式
Hook 在 settings.json 中配置:
// ~/.claude/settings.json 或 .claude/settings.json { "hooks": { "PreToolUse": [ { "matcher": { "tool_name": "Bash" }, "command": "python3 check_bash_safety.py" } ], "PostToolUse": [ { "matcher": { "tool_name": "FileWriteTool" }, "command": "./scripts/auto-format.sh" } ], "SessionStart": [ { "command": "echo 'Session started' >> ~/.claude/session.log" } ] } }
执行机制
Hook 通过子进程执行,输入通过 stdin 传递,输出通过 stdout/stderr 收集:
// 执行流程 (execPromptHook.ts) async function executePromptHook(hookConfig, input) { // 1. 启动子进程 const proc = spawn(hookConfig.command, { shell: true }); // 2. 传入 JSON 输入 proc.stdin.write(JSON.stringify(input)); proc.stdin.end(); // 3. 收集输出 const stdout = await readStream(proc.stdout); const stderr = await readStream(proc.stderr); // 4. 根据 exit code 决定行为 const exitCode = await proc.exitCode; return { exitCode, stdout, stderr }; }
Exit Code 约定
Exit 0 — 成功
PreToolUse: 静默通过
PostToolUse: stdout 以 transcript 模式显示
PreCompact: stdout 追加到压缩指令
Exit 2 — 特殊控制
PreToolUse: 阻止工具执行,stderr 作为原因显示给模型
PreCompact: 阻止压缩
TaskCreated: 阻止任务创建
其他 Exit Code
stderr 仅显示给用户(不传递给模型)。用于日志记录和调试信息。
异步 Hook 注册表
AsyncHookRegistry 管理长时间运行的 Hook:
- 注册:
registerPendingAsyncHook()注册 Hook,设置超时(默认 15 秒) - 进度追踪: 每秒发出
HookProgressEvent - 轮询: 每个 query turn 调用
checkForAsyncHookResponses()检查完成状态 - 完成: 读取 stdout/stderr,解析 JSON 响应,触发
HookResponseEvent - 清理: 完成的 Hook 从 pendingHooks Map 中移除