Hooks 使用指南
Hook 是绑在"事件"上的自动脚本。事件一发生(会话开始、文件被编辑、工具调用完成等),Claude Code 自动执行你配置的 shell 命令。Claude 本身不参与决策,跳不过、也漏不掉。
目录
- Hook 是什么
- Hook 和 Skill 的核心差异
- Hook 配置文件在哪
- Hook 事件类型详解
- 关键机制:stdout 如何回到主对话
- 五个典型应用场景
- 从零建一个 Hook
- 四大坑与规避
- 常见问题
Hook 是什么
一个通俗的类比:
Hook 就像家里的"感应灯"。你不用主动按开关,人一走过感应器就自动亮。灯亮不需要"决策",触发条件(人经过)满足就一定亮。
对应到 Claude Code:
- 事件 = 人经过感应器(会话开始、文件被编辑等)
- Hook 脚本 = 灯的行为(跑一段命令、打个日志、给个提醒)
- 触发 = 自动的,Claude 决定不了
为什么需要 hook:有些事情"必须做"、“不能漏”、“不需要智能判断”。比如:
- 每次开会话都要检查环境依赖,漏一次就出错
- 每次改完代码要记录哪些文件被改过,方便后续统一检查
- 用户消息里包含特定关键词时打个埋点
这些事如果做成 skill,靠 Claude “记得调用”,就会有漏;做成 hook,只要事件触发,脚本一定跑。
Hook 和 Skill 的核心差异
一张表说清楚:
| 维度 | Hook | Skill |
|---|---|---|
| 触发 | 事件自动触发 | Claude 判断或用户 /命令 |
| 参与者 | Claude Code 进程 + Shell | Claude 本人 |
| 能否跳过 | 不能(事件到就跑) | 能(Claude 觉得没必要就不用) |
| 有没有 LLM 智能 | 没有,纯 shell 逻辑 | 有,Claude 边执行边思考 |
| 产出去向 | stdout 可注入上下文 | 全部在主对话 |
| 典型形态 | JSON 配置 + shell 脚本 | Markdown 说明书 |
| 失败会怎样 | 影响事件流转(阻断或警告) | Claude 尝试恢复或问用户 |
记忆口诀:
- 需要 智能判断 → Skill
- 需要 绝对不漏 → Hook
Hook 配置文件在哪
Hook 通过 JSON 配置注册。有三个层级:
1. 项目级
<项目根>/.claude/settings.json 或 .claude/hooks/hooks.json(插件形式)
跟仓库走,团队共享。
2. 用户级
~/.claude/settings.json
对当前用户所有项目生效。
3. 插件级
插件目录下 hooks/hooks.json
插件安装后自动生效。
配置格式:
{
"hooks": {
"<事件名>": [
{
"matcher": "<匹配条件>",
"hooks": [
{
"type": "command",
"command": "bash 你的脚本.sh",
"timeout": 5000,
"description": "这个 hook 做什么用"
}
]
}
]
}
}
matcher:什么情况下触发,*是全部;对于 PostToolUse 可以写Edit|Write之类的工具名command:真正执行的 shell 命令timeout:超时时间(毫秒)description:给人看的说明,不影响执行
Hook 事件类型详解
主流的几个事件:
SessionStart —— 会话开始
什么时候触发:用户新开一个 Claude Code 会话时。
能干什么:
- 检查基础环境(Node 版本、依赖是否装了)
- 预热缓存(后台构建依赖图)
- 注入项目的当前状态到上下文
通俗类比:早上到公司刷卡开门 + 打开电脑 + 泡咖啡的一整套开工准备。
UserPromptSubmit —— 用户发消息
什么时候触发:用户每次发一条消息给 Claude 时。
能干什么:
- 检测关键词,给 Claude 一些提示(比如"用户提到部署,建议查看部署文档")
- 打埋点,记录用户在做什么
- 拦截敏感操作(比如用户想让 Claude 删数据库时先弹警告)
通俗类比:给每一封新邮件打标签、扫毒、自动归类。
PostToolUse —— 工具调用完成
什么时候触发:Claude 用完某个工具(Edit / Write / Bash 等)之后。
能干什么:
- 记录 Claude 改了哪些文件
- 检查改动是否违反某些规则(比如警告
console.log) - 打埋点
通俗类比:施工现场每完成一个工序就自动拍照存档。
matcher 可以限定只对哪些工具生效:
Edit只对 Edit 工具Write|Edit|MultiEdit三个工具都触发*所有工具
PostToolUseFailure —— 工具调用失败
什么时候触发:某个工具执行失败时。
能干什么:
- 打埋点统计失败率
- 失败时给 Claude 一个补救提示
通俗类比:施工出问题时自动通知项目经理。
Stop —— 会话即将结束
什么时候触发:Claude 认为任务完成、想结束对话时。
能干什么:
- 提交前的最后检查(还有 lint 问题没修?还有 uncommitted change?)
- 阻止结束,把 Claude 拉回来继续做事
- 打埋点(flush 埋点缓冲)
通俗类比:下班前的关灯锁门检查清单。
PreToolUse —— 工具调用前
什么时候触发:Claude 要调用某个工具之前。
能干什么:
- 阻断危险操作(比如禁止 Claude 跑
rm -rf) - 修改工具参数
- 打埋点
生产项目常常用来做"权限管控"。
关键机制:stdout 如何回到主对话
这是 Hook 最容易被忽视但最重要的能力:Hook 脚本的 stdout 会被注入主对话上下文,让 Claude 感知到。
举个具体例子
假设你写一个 UserPromptSubmit hook:
#!/bin/bash
# 收到用户 prompt via stdin,JSON 格式
prompt=$(jq -r '.prompt // ""')
if echo "$prompt" | grep -qiE "(review|审查)"; then
echo "💡 提示:项目有 code-reviewer subagent 可用,建议通过 Agent 工具调用。"
fi
exit 0
流程是这样的:
- 用户说"帮我 review 一下代码"
- Claude Code 触发 UserPromptSubmit,把
{"prompt": "帮我 review 一下代码"}通过 stdin 传给你的脚本 - 脚本匹配到 “review” 关键词,往 stdout 输出提示文本
- Claude Code 把这段 stdout 追加到 Claude 看到的上下文里
- Claude 除了看到用户原话,还看到了"提示:可以用 code-reviewer subagent"
- Claude 多半会照着提示调 subagent
通俗理解:Hook 就像一个"给 Claude 递纸条的助理"。用户说话时,助理偷偷塞张纸条给 Claude “别忘了这件事”。Claude 不一定完全按纸条走(它还是有自己的判断),但注意力会被引导过去。
stdout 注入的几个事件
哪些事件的 stdout 会进上下文?主流的几个:
- SessionStart:会话开始时一次性注入
- UserPromptSubmit:附在用户 prompt 后
- PostToolUse:附在工具结果后
stdout 注入 vs 不注入
不是所有 hook 事件的 stdout 都注入。像 Stop 主要用来做副作用(打埋点、清理),stdout 一般不影响主对话。具体以官方文档为准,但核心心智是:需要影响 Claude 判断的用 stdout 注入型事件,只做副作用的可以任意事件。
五个典型应用场景
场景 1:会话开始检查环境
痛点:团队里有的同学本地 Node 版本不对,跑起来一堆报错,浪费时间排查。
方案:SessionStart hook 里检查 Node/pnpm/工具链版本,不对就通过 stdout 提示"你的 Node 版本是 X,项目要求 Y,建议先切换"。
为什么用 hook:漏一次就要浪费半小时排查环境问题。
场景 2:改代码时自动打标签
痛点:会话结束后想统一跑 lint,但全项目扫太慢。
方案:PostToolUse hook 匹配 Write|Edit,每次改完就把文件路径追加到 .claude/changed-files.log。会话结束时的检查脚本直接读这个文件,精确定位改过的文件。
为什么用 hook:追踪必须一次不漏,否则统计不准。
场景 3:拦截危险操作
痛点:担心 Claude 误跑 rm -rf 或者 git push --force。
方案:PreToolUse hook 匹配 Bash,检查命令内容,遇到危险模式(rm -rf /、--force 推 main 等)就返回非零退出码阻断。
为什么用 hook:安全检查必须硬控制,不能靠 Claude 自觉。
场景 4:给 Claude 塞项目上下文
痛点:每次开会话都得手动告诉 Claude “现在在做需求 A、分支 B、上次做到 C”。
方案:SessionStart hook 跑一个脚本,从 git 分支名、最近提交、当前 openspec change 目录里抽取上下文,通过 stdout 塞给 Claude。
为什么用 hook:需要"每次都自动做",做成 skill 得每次手动 /load-context。
场景 5:埋点统计使用情况
痛点:不知道团队里哪些 skill 用得多、哪些 skill 从来没人用、哪些 skill 经常失败。
方案:各种 hook 事件里挂埋点脚本,把关键数据(用了什么 skill、跑了什么工具、失败原因)发到内部埋点系统。
为什么用 hook:埋点必须无侵入、无遗漏。
从零建一个 Hook
举个具体例子:用户消息里包含"部署"字样时,提醒查看部署文档。
步骤 1:写脚本
.claude/hooks/deploy-reminder.sh:
#!/bin/bash
# UserPromptSubmit hook:检测部署意图并提示
# 从 stdin 读 JSON
input=$(cat)
prompt=$(echo "$input" | jq -r '.prompt // ""')
# 关键词匹配
if echo "$prompt" | grep -qiE "(部署|deploy|上线|发版)"; then
cat <<EOF
💡 提示:你提到了部署相关操作。
- 部署流程文档:docs/deployment.md
- 生产环境部署需要走 CI 审批,不能直接在本地跑
EOF
fi
exit 0
给它加执行权限:
chmod +x .claude/hooks/deploy-reminder.sh
步骤 2:注册到 settings.json
.claude/settings.json:
{
"hooks": {
"UserPromptSubmit": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PROJECT_DIR}/.claude/hooks/deploy-reminder.sh",
"timeout": 3000,
"description": "检测部署意图并提示查看文档"
}
]
}
]
}
}
步骤 3:验证
在 Claude Code 里说"帮我部署到测试环境",看看 Claude 会不会先提到文档。
四大坑与规避
坑 1:每次都注入内容,token 爆炸
症状:UserPromptSubmit hook 每次都输出一大段提示,会话跑几十轮后上下文塞满 hook 的输出,token 疯狂消耗。
规避:
- 只在匹配到条件时输出,不匹配就
exit 0无输出 - 输出尽量短,一两行足矣
- 匹配条件要精准,别用
grep code这种宽泛的 - 定期审计:搜一下 hook 输出的关键词在最近几次会话上下文里出现的频率,太高就收紧匹配
坑 2:Hook 脚本执行慢,会话启动变慢
症状:SessionStart 里挂了个跑 30 秒的脚本,用户每次开会话都要等半分钟。
规避:
- 慢脚本用
&放后台跑 - 或者拆成两步:立即返回一个"正在准备"的提示,慢动作后台做
- 严格设
timeout,避免脚本卡死拖累整个会话
坑 3:Hook 硬编码路径失效
症状:脚本里写死 /Users/xxx/project/foo/bar.sh,换台机器或换项目就跑不了。
规避:
- 项目内脚本用
${CLAUDE_PROJECT_DIR}变量指向项目根 - 插件里的脚本用
${CLAUDE_PLUGIN_ROOT} - 用户级脚本用
~/或$HOME
坑 4:Hook 里做太多 Claude 该做的事
症状:想让 hook “分析 diff 内容然后决定 review 策略”。但 hook 是 shell,没 LLM 能力,只能做字符串处理。做出来的东西又复杂又不聪明。
规避:
想清楚"这件事需不需要智能判断":
- 需要智能 → 让 hook 只做"触发提示",真正的智能交给 skill / subagent
- 不需要智能(规则明确的机械动作) → hook 全权处理
Hook 的定位是"触发器和守门员",不是"决策者"。
常见问题
Q1:Hook 报错会怎样?
看事件类型:
- PreToolUse 报错(非零退出码)会阻断工具调用
- 其他事件 通常不阻断,但错误信息可能显示给用户
- 建议 hook 脚本内部自己捕获错误,别让整个会话炸掉
Q2:Hook 能读用户之前说过的话吗?
只能读当前触发事件带来的数据(比如 UserPromptSubmit 事件的 stdin 是 JSON,里面有本次的 prompt)。想读历史需要自己去看 Claude Code 的日志文件(不推荐,格式不稳定)。
Q3:Hook 和 Skill 能不能配合?
非常推荐配合。经典模式:
- Hook 在幕后做机械动作(追踪、准备缓存、打埋点)
- Skill 在台前做主流程,直接用 hook 准备好的东西
Q4:多个 hook 挂同一个事件,执行顺序如何?
按 JSON 配置里的数组顺序执行。上一个的输出不会影响下一个的输入(每个 hook 都独立收到事件数据)。
Q5:怎么调试 hook?
- 脚本里加
set -x或echo "debug: xxx" >&2(stderr 不进上下文,只显示在终端) - 手动模拟触发:
echo '{"prompt": "test"}' | bash your-hook.sh - 看 Claude Code 的会话日志(存在
~/.claude/projects/下)
Q6:Hook 会影响性能吗?
- SessionStart:影响会话启动速度,慢脚本务必放后台
- UserPromptSubmit:影响每轮响应速度,脚本必须快(几十毫秒级)
- PostToolUse:影响工具执行完到 Claude 继续思考的间隔,也要快
一般脚本控制在 100ms 内没感觉,超过 1 秒就明显了。
快速参考卡
┌─────────────────────────────────────────────────────────┐
│ Hook = 事件钩子 │
│ │
│ 核心事件: │
│ SessionStart 会话开始(做环境准备) │
│ UserPromptSubmit 用户发消息(做提示/拦截) │
│ PreToolUse 工具调用前(做权限检查) │
│ PostToolUse 工具调用后(做追踪/检查) │
│ Stop 会话结束(做发前检查) │
│ │
│ 能力: │
│ - stdout 会注入主对话(让 Claude 看到) │
│ - 非零退出码可阻断(PreToolUse 类) │
│ - matcher 可过滤(只对某些工具生效) │
│ │
│ 配置位置: │
│ 项目:.claude/settings.json │
│ 用户:~/.claude/settings.json │
│ 插件:<插件>/hooks/hooks.json │
│ │
│ 什么时候用 hook(不是 skill): │
│ - 必须每次都跑,不能漏 │
│ - 事件驱动而不是意图驱动 │
│ - 纯规则匹配,不需要 LLM 智能 │
└─────────────────────────────────────────────────────────┘
下一步
- 📘 Subagents 使用指南 —— 独立会话怎么用
- 📙 知识库方案选型 —— Hook + Skill 组合方案
更多推荐



所有评论(0)