Hooks 是什么
一句话:你预设的自动化脚本,在 Claude Code 的特定时刻自动触发。
Claude Code 的工作流有很多"时刻"。会话开始、工具调用前、工具调用后、回复完成……
Hooks 在这些时刻插入你的逻辑。Claude Code 走到那个点,自动执行。
不用你手动干预,不用 Claude 自己决定。确定性执行,不靠 LLM 判断。

Hook 类型
Hooks 有 4 种类型:
| 类型 | 说明 | 适用场景 |
|---|---|---|
| command | 跑 shell 命令 | 格式化、lint、通知、拦截 |
| http | POST 请求到 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 | 会话终止 |
Stop | Claude 完成响应 |
StopFailure | API 错误导致回合结束 |
工具调用
| 事件 | 触发时机 |
|---|---|
PreToolUse | 工具执行前,可拦截 |
PostToolUse | 工具执行后 |
PostToolUseFailure | 工具执行失败后 |
PermissionRequest | 权限对话框弹出时 |
用户输入
| 事件 | 触发时机 |
|---|---|
UserPromptSubmit | 提交 prompt 后、Claude 处理前 |
Subagent 和任务
| 事件 | 触发时机 |
|---|---|
SubagentStart | subagent 生成时 |
SubagentStop | subagent 完成时 |
TaskCreated | 创建任务时 |
TaskCompleted | 任务完成时 |
TeammateIdle | agent team 队友即将空闲 |
上下文和配置
| 事件 | 触发时机 |
|---|---|
PreCompact | 上下文压缩前 |
PostCompact | 上下文压缩后 |
InstructionsLoaded | CLAUDE.md / rules 文件加载时 |
ConfigChange | 配置文件变更时 |
文件和环境
| 事件 | 触发时机 |
|---|---|
CwdChanged | 工作目录变更时 |
FileChanged | 监视的文件变更时 |
WorktreeCreate | worktree 创建时 |
WorktreeRemove | worktree 移除时 |
通知和 MCP
| 事件 | 触发时机 |
|---|---|
Notification | 发送通知时 |
Elicitation | MCP 服务器请求用户输入时 |
ElicitationResult | 用户响应 MCP elicitation 后 |
22 个不用全记。日常最常用 5 个:
SessionStart(初始化)。覆盖 80% 场景PreToolUse(拦截)PostToolUse(后处理)Notification(通知)Stop(收尾)
通信方式
Hooks 怎么和 Claude Code "对话"?靠三样东西:stdin、stdout、退出码。
输入:stdin 接收 JSON
Claude Code 把事件信息打包成 JSON,通过 stdin 送过来。
比如 Claude 要跑 npm test,PreToolUse hook 收到的是:
{
"session_id": "abc123",
"cwd": "/Users/you/project",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}
通用字段:session_id、cwd、hook_event_name。
每个事件还有自己的字段,比如工具事件有 tool_name 和 tool_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 分钟能上手。
参考资料
如果你觉得这篇文章对你有帮助,记得点赞、分享,关注,万分感谢!