主题
06.5 Hooks 生命周期钩子
Hook 是把"概率性的提示词"变成"确定性的强制"的唯一途径。
读完你能做什么:在关键生命周期事件上注入你自己的脚本,实现提示词做不到的硬性保证。
一、为什么需要 Hook
本手册反复强调一条界线:
CLAUDE.md 和提示词是"上下文",Claude 会尽力遵守但不保证。Hook 是"代码",在固定的生命周期事件上必然执行。
| 你想要 | 提示词能做吗 | Hook 能做吗 |
|---|---|---|
| 倾向于用某种代码风格 | ✅ | 不必 |
| 保证每次编辑后跑格式化 | ❌ 概率性 | ✅ 确定性 |
| 保证危险命令被拦截 | ❌ | ✅ |
| 保证提交前跑测试 | ❌ | ✅ |
| 保证类型错误立刻回填给 Claude | ❌ | ✅ |
任何"必须 100%"的需求,都该用 hook。
二、三层配置结构
Hook 配置有三层嵌套:
text
1. 选一个 hook 事件(何时触发):PreToolUse、Stop 等
2. 加一个 matcher(过滤条件):只对 Bash 工具
3. 定义 handler(执行什么):shell 命令 / HTTP / MCP 工具 / prompt / agent2.1 基本形态
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/check.sh"
}
]
}
]
}
}2.2 配置位置与作用域
| 位置 | 作用域 | 可共享 |
|---|---|---|
~/.claude/settings.json | 你所有项目 | 否 |
.claude/settings.json | 单个项目 | 是(可提交) |
.claude/settings.local.json | 单个项目 | 否(gitignore) |
| 管理策略设置 | 组织级 | 是(管理员) |
Plugin 的 hooks/hooks.json | 插件启用时 | 是 |
| Skill/Agent frontmatter | 组件激活时 | 是 |
三、核心 Hook 事件
Claude Code 的 hook 事件很多,这里列最常用的:
| 事件 | 触发时机 | matcher 过滤 | 典型用途 |
|---|---|---|---|
PreToolUse | 工具执行前 | 工具名 | 拦截危险操作、注入上下文 |
PostToolUse | 工具执行后 | 工具名 | 自动格式化、类型检查 |
UserPromptSubmit | 你提交消息时 | 无 | 注入额外上下文 |
SessionStart | 会话开始 | 启动方式 | 初始化环境 |
SessionEnd | 会话结束 | 结束原因 | 清理 |
Stop | Claude 完成回复 | 无 | 完成通知、最终检查 |
PreCompact / PostCompact | 压缩前后 | 触发方式 | 保存/恢复状态 |
Notification | 各类通知 | 通知类型 | 自定义提醒 |
SubagentStart / SubagentStop | 子代理起止 | agent 类型 | 监控子代理 |
3.1 matcher 的三种解析方式
| matcher 值 | 解析为 | 示例 |
|---|---|---|
"*" / "" / 省略 | 匹配所有 | 每次都触发 |
| 只含字母数字下划线连字符空格逗号竖线 | 精确字符串(或列表) | Bash、Edit|Write |
| 含其他字符 | JavaScript 正则(不锚定) | ^Notebook、mcp__memory__.* |
坑:正则默认不锚定。Edit.* 会匹配 NotebookEdit。要整串匹配用 ^Edit$。
四、Hook 的输入输出协议
4.1 输入:stdin 的 JSON
Claude Code 把事件信息作为 JSON 从 stdin 传给你的脚本:
json
{
"tool_name": "Bash",
"tool_input": { "command": "rm -rf /tmp/build" }
}用 jq 解析(脚本依赖 jq,确保它在 PATH 里):
bash
COMMAND=$(jq -r '.tool_input.command')4.2 输出:控制决策的 JSON
PreToolUse hook 可以通过 stdout 返回决策:
json
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook"
}
}permissionDecision 取值:allow / deny / ask。
exit 0 且无输出 = 无决策,走正常权限流程。也就是说 hook 可以拒绝,但沉默不代表批准。
五、四个可直接用的 Hook
5.1 拦截危险命令(PreToolUse)
bash
#!/usr/bin/env bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -qE 'rm -rf|:(){ :|:& };:'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "破坏性命令被 hook 拦截"
}
}'
else
exit 0
fibash
chmod +x .claude/hooks/block-rm.sh # macOS/Linux 需可执行5.2 编辑后自动格式化(PostToolUse)
bash
#!/usr/bin/env bash
# .claude/hooks/auto-format.sh
FILE=$(jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0
case "$FILE" in
*.ts|*.tsx|*.js|*.jsx) npx prettier --write "$FILE" 2>/dev/null ;;
*.go) gofmt -w "$FILE" ;;
*.py) black -q "$FILE" 2>/dev/null ;;
*.rs) rustfmt "$FILE" 2>/dev/null ;;
esac
exit 0json
{
"hooks": {
"PostToolUse": [
{ "matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": ".claude/hooks/auto-format.sh" }] }
]
}
}5.3 编辑后类型检查并回填错误(PostToolUse)
这个 hook 让幻觉在产生的下一秒就被抓住 —— 编造的 API 会导致类型错误,错误直接回到 Claude 的上下文:
bash
#!/usr/bin/env bash
# .claude/hooks/typecheck.sh
FILE=$(jq -r '.tool_input.file_path // empty')
case "$FILE" in
*.ts|*.tsx)
OUT=$(npx tsc --noEmit 2>&1 | head -30)
if [ -n "$OUT" ]; then
jq -n --arg out "$OUT" '{
hookSpecificOutput: {
hookEventName: "PostToolUse",
additionalContext: ("类型检查失败,必须修复:\n" + $out)
}
}'
fi
;;
esac
exit 0additionalContext 把内容注入 Claude 的下一轮上下文,它会看到错误并去修。
5.4 提交前强制跑测试(PreToolUse)
bash
#!/usr/bin/env bash
# .claude/hooks/pre-commit-test.sh
CMD=$(jq -r '.tool_input.command // empty')
# 只在 git commit 时触发
if echo "$CMD" | grep -qE '^git commit'; then
if ! npm test > /tmp/test.log 2>&1; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "测试未通过,禁止提交。先修复失败的测试。"
}
}'
exit 0
fi
fi
exit 0效果:无论 Claude 多想提交,测试不过就是提交不了。这比在 CLAUDE.md 里写"提交前跑测试"可靠得多。
六、Hook 的其他 handler 类型
除了 command(shell),handler 还能是:
| type | 作用 |
|---|---|
command | 执行 shell 命令(最常用) |
| HTTP endpoint | 调用一个 HTTP 服务(受 allowlist 限制) |
| MCP 工具 | 调用某个 MCP 工具 |
| prompt | 注入一段提示词 |
| agent | 派一个子代理 |
HTTP hook 受 allowedHttpHookUrls 和 httpHookAllowedEnvVars 两个 allowlist 约束,保证安全。
七、平台注意事项
7.1 Windows
Windows 上用 PowerShell 写 hook,注册时:
json
{ "type": "command", "command": "powershell.exe -File .claude/hooks/check.ps1" }7.2 依赖 jq
页面上的 Bash 示例都用 jq 解析 JSON。确保 jq 已安装且在 PATH 里。
7.3 可执行权限
macOS/Linux 上脚本要 chmod +x。
八、调试 Hook
text
> /hooks # 查看当前 hook 配置bash
claude --debug "api,hooks" # 开启 hook 调试日志用 InstructionsLoaded hook 可以记录到底哪些指令文件被加载了,用于排查 CLAUDE.md/rules 的加载问题。
九、Hook 设计原则
| 原则 | 说明 |
|---|---|
| 快 | Hook 在关键路径上,慢 hook 拖慢每次操作。重活异步做 |
| 静默成功 | 成功时 exit 0 无输出,只在需要拦截/注入时说话 |
| 精确匹配 | 用 if 条件避免不必要的进程 spawn |
| 失败安全 | Hook 脚本自身出错时,想清楚默认放行还是拦截 |
| 可组合 | 多个来源的 hook 会合并,不会互相覆盖 |
9.1 用 if 减少开销
Hook 配置支持 if 条件,只在真正需要时才 spawn 脚本进程:
json
{
"matcher": "Bash",
"if": "Bash(rm *)",
"hooks": [{ "type": "command", "command": ".claude/hooks/block-rm.sh" }]
}matcher 先筛工具,if 再筛具体命令,都匹配才运行脚本 —— npm test 这类命令连脚本都不会启动。
十、一套完整的项目 Hook 配置
.claude/settings.json:
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": ".claude/hooks/guard-git.sh" }]
},
{
"matcher": "Bash",
"if": "Bash(rm *)",
"hooks": [{ "type": "command", "command": ".claude/hooks/block-rm.sh" }]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": ".claude/hooks/auto-format.sh" },
{ "type": "command", "command": ".claude/hooks/typecheck.sh" }
]
}
]
}
}这套配置实现了:危险命令拦截 + 自动格式化 + 编辑后类型检查回填。全部是确定性的,不依赖 Claude 的"自觉"。