Skip to content

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 / agent

2.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会话结束结束原因清理
StopClaude 完成回复完成通知、最终检查
PreCompact / PostCompact压缩前后触发方式保存/恢复状态
Notification各类通知通知类型自定义提醒
SubagentStart / SubagentStop子代理起止agent 类型监控子代理

3.1 matcher 的三种解析方式

matcher 值解析为示例
"*" / "" / 省略匹配所有每次都触发
只含字母数字下划线连字符空格逗号竖线精确字符串(或列表)BashEdit|Write
含其他字符JavaScript 正则(不锚定)^Notebookmcp__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
fi
bash
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 0
json
{
  "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 0

additionalContext 把内容注入 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 受 allowedHttpHookUrlshttpHookAllowedEnvVars 两个 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 的"自觉"。


延伸阅读

基于 VitePress 构建 · 内容采用原作者授权