Claude Code Hooks:22个自动化钩子,5分钟上手

0 阅读5分钟

image.png

Hooks 是什么

一句话:你预设的自动化脚本,在 Claude Code 的特定时刻自动触发。

Claude Code 的工作流有很多"时刻"。会话开始、工具调用前、工具调用后、回复完成……

Hooks 在这些时刻插入你的逻辑。Claude Code 走到那个点,自动执行。

不用你手动干预,不用 Claude 自己决定。确定性执行,不靠 LLM 判断。

Hook 类型

Hooks 有 4 种类型:

类型说明适用场景
command跑 shell 命令格式化、lint、通知、拦截
httpPOST 请求到 URL远程审计、webhook、外部服务
prompt让 LLM 做单轮判断需要"判断力"的决策
agent生成 subagent 多轮验证复杂验证,比如跑完测试再决定

前两种是确定性的,你写什么逻辑就执行什么。

后两种有意思 用 AI 审查 AI。prompt hook 让模型判断"这次修改符不符合规范",agent hook 更猛,能调工具、读文件、跑测试,验证完再给结论。

如何配置

配置在 JSON 设置文件里。三层结构:

1. 选事件 → 什么时候触发
2. 加匹配器 → 过滤条件(可选)
3. 定义处理程序 → 触发后干什么

配置放哪

位置范围能提交到仓库吗
~/.claude/settings.json所有项目
.claude/settings.json单个项目
.claude/settings.local.json单个项目否(gitignored)

全局的放 ~/.claude/settings.json,项目级的放 .claude/settings.json

配置格式

最简单的例子 Claude 干完活弹个桌面通知

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification "Claude Code 需要你" with title "Claude Code"'"
          }
        ]
      }
    ]
  }
}

拆开看:

  • Notification —— 事件名,Claude 发通知时触发
  • matcher: "" —— 空字符串,匹配所有通知
  • type: "command" —— 跑 shell 命令
  • command —— 具体命令(macOS 原生通知)

配好后,Claude 等你输入时桌面直接弹通知。不用盯终端了。

匹配器(Matcher)

匹配器是正则表达式,过滤触发条件。

比如只在 Claude 写文件后跑格式化:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

"Edit|Write" 只匹配 Edit 和 Write 工具。Bash、Read 这些不触发。

MCP 工具也能匹配,命名格式 mcp__<server>__<tool>。比如 mcp__github__.* 匹配 GitHub 服务器的所有工具。

22 个 Hook 事件

数量看着吓人。按功能分几类就好记了。

会话生命周期

事件触发时机
SessionStart会话开始或恢复
SessionEnd会话终止
StopClaude 完成响应
StopFailureAPI 错误导致回合结束

工具调用

事件触发时机
PreToolUse工具执行,可拦截
PostToolUse工具执行
PostToolUseFailure工具执行失败后
PermissionRequest权限对话框弹出时

用户输入

事件触发时机
UserPromptSubmit提交 prompt 后、Claude 处理前

Subagent 和任务

事件触发时机
SubagentStartsubagent 生成时
SubagentStopsubagent 完成时
TaskCreated创建任务时
TaskCompleted任务完成时
TeammateIdleagent team 队友即将空闲

上下文和配置

事件触发时机
PreCompact上下文压缩前
PostCompact上下文压缩后
InstructionsLoadedCLAUDE.md / rules 文件加载时
ConfigChange配置文件变更时

文件和环境

事件触发时机
CwdChanged工作目录变更时
FileChanged监视的文件变更时
WorktreeCreateworktree 创建时
WorktreeRemoveworktree 移除时

通知和 MCP

事件触发时机
Notification发送通知时
ElicitationMCP 服务器请求用户输入时
ElicitationResult用户响应 MCP elicitation 后

22 个不用全记。日常最常用 5 个:

  • SessionStart(初始化)。覆盖 80% 场景
  • PreToolUse(拦截)
  • PostToolUse(后处理)
  • Notification(通知)
  • Stop(收尾)

通信方式

Hooks 怎么和 Claude Code "对话"?靠三样东西:stdinstdout退出码

输入:stdin 接收 JSON

Claude Code 把事件信息打包成 JSON,通过 stdin 送过来。

比如 Claude 要跑 npm testPreToolUse hook 收到的是:

{
  "session_id": "abc123",
  "cwd": "/Users/you/project",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test"
  }
}

通用字段:session_idcwdhook_event_name

每个事件还有自己的字段,比如工具事件有 tool_nametool_input

输出:退出码

脚本通过退出码告诉 Claude Code 怎么办:

退出码含义
0放行,stdout 的 JSON 会被解析
2阻止,stderr 内容反馈给 Claude
其他忽略,继续执行

记两个数字就够了。0 放行,2 拦截

进阶:JSON 精细控制

退出码只有放行和拦截。想更精细?exit 0 后在 stdout 输出 JSON:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "用 rg 代替 grep,性能更好"
  }
}

三种决策:

  • "allow" —— 放行,跳过权限提示
  • "deny" —— 拒绝,告诉 Claude 原因
  • "ask" —— 照常弹权限提示给用户

3 个实用案例

案例1:拦截危险命令

Claude 要跑 rm -rf?自动拦住:

#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(cat | jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
  echo "危险命令被拦截" >&2
  exit 2
fi

exit 0

配置:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}

脚本从 stdin 读 JSON,提取命令,发现 rm -rf 就 exit 2 拦截。stderr 的提示反馈给 Claude,它会换个方式处理。

案例2:压缩后补上下文

Claude Code 聊久了会压缩上下文,重要信息可能丢失。用 SessionStart hook 在压缩后自动补回来:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "echo '提醒:用 Bun 不用 npm。提交前跑 bun test。当前在做:auth 重构。'"
          }
        ]
      }
    ]
  }
}

matcher 设成 "compact" 只在压缩后触发。stdout 输出的文本直接进 Claude 上下文。

案例3:AI 审查 AI

Claude 说"我做完了"。真的吗?让 prompt hook 再审一遍:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "prompt",
            "prompt": "检查所有任务是否完成。如果没完成,返回 {"ok": false, "reason": "还需要做什么"}。"
          }
        ]
      }
    ]
  }
}

模型返回 "ok": false,Claude 继续干。

返回 "ok": true 才真正停下。用 AI 监督 AI,靠谱。

调试

  • /hooks —— 查看当前所有 hook 配置
  • Ctrl+O —— 详细模式,看 hook 执行输出
  • claude --debug —— 完整调试信息
  • 手动测试:echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh

之前写 RTK 那篇文章提过,RTK 的命令改写就是通过 Hook 实现的。

一条 rtk init --global,往 ~/.claude/settings.json 写一个 hook,之后 git status 自动变成 rtk git status。省 token,无感切换。

22 个事件看着多,常用的就 5 个。配置也就是 JSON 三层嵌套,5 分钟能上手。

参考资料


如果你觉得这篇文章对你有帮助,记得点赞、分享,关注,万分感谢!