Hooks:在生命周期节点强制执行动作
提示词是「希望它这么做」,钩子是「必定这么做」。这是本手册最技术的一节,但有三个用法值得任何人配一次。
这是全手册最技术的一节。 但先说结论:
有三个钩子值得任何人配一次,每个不到十行。 配完就不用再管它了。
要不要往下读完整节,看你需不需要第四个。
钩子是什么
在特定时刻自动执行的一条命令。
它和写在 CLAUDE.md 里的要求的区别:
CLAUDE.md / 技能 |
钩子 | |
|---|---|---|
| 性质 | 希望它这么做 | 必定这么做 |
| 谁执行 | 模型(它得先理解,再记得) | 程序 |
| 会不会漏 | 会 | 不会 |
| 占上下文 | 占 | 不占 |
一句话:需要判断的用提示词,需要保证的用钩子。
值得任何人配的三个
① 它需要你的时候提醒你
最实用的一个。 你派了个长任务,切去做别的,它做完或者需要你批准时,你不知道。
macOS:
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code 需要你\" with title \"Claude Code\"'"
}
]
}
]
}
}
放进 ~/.claude/settings.json。
更简单的办法:如果只是想要个响声,不用配钩子——在设置里把
preferredNotifChannel设成"terminal_bell"就行。
② 挡住不该碰的文件
比在 CLAUDE.md 里写「不要动这些文件」硬得多。
先写一个脚本 .claude/hooks/protect.sh:
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
PROTECTED=(".env" "client-data/" "台账")
for p in "${PROTECTED[@]}"; do
if [[ "$FILE_PATH" == *"$p"* ]]; then
echo "已阻止:$FILE_PATH 命中受保护规则 '$p'" >&2
exit 2
fi
done
exit 0
记得给执行权限:chmod +x .claude/hooks/protect.sh
然后在设置里挂上:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect.sh"
}
]
}
]
}
}
这个钩子有一个很硬的性质,值得单独说:
PreToolUse钩子在所有权限检查之前触发,在每一种权限模式下都触发——包括bypassPermissions。返回拒绝的钩子,在跳过权限的模式下也照样能挡住。反过来不成立:钩子说「允许」,挡不住设置里的拒绝规则。钩子只能收紧,不能放松。
③ 压缩之后把关键信息塞回去
做长任务时很有用。 压缩会丢掉一些东西,这个钩子在压缩后自动把要点补回去:
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo '提醒:原话摘录必须逐字。受访者一律用编号。输出到 coding.md。'"
}
]
}
]
}
}
原理是:
SessionStart这类事件下,钩子打印到标准输出的任何内容都会进入它的上下文。
配置放哪
| 位置 | 范围 |
|---|---|
~/.claude/settings.json |
你的所有项目 |
.claude/settings.json |
这个项目(可以进版本库) |
.claude/settings.local.json |
这个项目,只有你 |
用 /hooks 查看当前配了哪些——它是只读的,要改还是得编辑文件,或者直接让它帮你改。
改完通常自动生效,不用重启。
结构
{
"hooks": {
"事件名": [
{
"matcher": "筛选条件",
"hooks": [
{ "type": "command", "command": "要执行的命令" }
]
}
]
}
}
⚠️ 一个常见的错误:已经有
hooks这个键的时候,新事件要作为兄弟键加进去,不要把整个对象替换掉。
常用的事件
事件有三十多个,日常用得上的是这几个:
| 事件 | 什么时候 | 能不能拦 |
|---|---|---|
PreToolUse |
动手之前 | 能 |
PostToolUse |
动完之后 | 不能(已经做了) |
Notification |
需要你的时候 | 不能 |
SessionStart |
会话开始或压缩之后 | 不能 |
UserPromptSubmit |
你按下回车之后、它开始处理之前 | 能 |
Stop |
它说完一轮之后 | 能 |
SessionEnd |
会话结束 | 不能 |
退出码决定一切
这是钩子机制里唯一必须记住的规则:
| 退出码 | 含义 |
|---|---|
0 |
正常。在 SessionStart 这类事件下,标准输出的内容会进入它的上下文 |
2 |
拦住。写到标准错误的内容会作为反馈发给它 |
| 其他 | 出错了但不拦,动作照常继续 |
⚠️ 不要混用两种方式。 要么用「退出码 2 + 标准错误」来拦,要么用「退出码 0 + 结构化 JSON」来做精细控制——退出码是 2 的时候,JSON 会被忽略。
还有一条容易误解的:
退出码 0 不等于「批准」。 对
PreToolUse来说,0 只是「我不表态」,正常的权限流程照走。
matcher 怎么写
| 写法 | 匹配 |
|---|---|
省略、""、"*" |
全部 |
"Bash" |
只有 Bash |
"Edit|Write" |
两者之一 |
"mcp__.*" |
所有 MCP 工具(这是正则) |
规则是:只含字母数字、下划线、连字符、空格、逗号、竖线的,当作精确匹配或列表;含其他字符的,当作正则。
区分大小写。 写
bash匹配不上Bash。
不同事件的 matcher 筛的东西不一样:工具类事件筛工具名,SessionStart 筛启动方式(startup / resume / clear / compact / fork),Notification 筛通知类型。
两个不用写脚本的类型
如果你不想碰 shell,还有两种:
让一个小模型来判断
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "检查这一轮是不是把所有要求的核对步骤都做了。没做完就回 {\"ok\": false, \"reason\": \"还差什么\"}。"
}
]
}
]
}
}
默认用 Haiku,很快也很便宜。
让一个带工具的代理来验证
{
"type": "agent",
"prompt": "验证输出文件里的条数和原始材料对得上。$ARGUMENTS"
}
这个能真的去跑命令查。 标注为实验性,可能变化。
怎么选:光看输入数据就能判断 → 用
prompt。需要去核对实际情况 → 用agent。
几个会踩到的坑
Stop 钩子的连续拦截有上限
Stop 钩子每次它说完话都会触发,不只是任务完成时。
如果你的 Stop 钩子一直拦着不让停,连续 8 次之后 Claude Code 会强行覆盖它。
正确的写法是检查那个标志位:
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0
fi
# 后面才是你的逻辑
钩子拦不住通过命令做的事
PreToolUse 挡住「编辑文件」这个工具,但挡不住它跑一个脚本、脚本自己去改文件。
要每一次文件变更都被看到,加一个 Stop 钩子每轮扫一遍工作树。
shell 配置里的 echo 会破坏 JSON
一个很隐蔽的坑: 钩子会启动一个 shell,如果你的 ~/.zshrc 里有无条件的 echo,那行输出会跑到钩子的 JSON 前面,导致解析失败。
改法是把它包进交互式判断:
if [[ $- == *i* ]]; then
echo "Shell ready"
fi
多个钩子的结果怎么合并
匹配同一事件的钩子会全部跑完,然后才合并结果。
一个钩子返回「拒绝」,不会阻止它的兄弟钩子执行。 别指望用一个钩子的拒绝去抑制另一个钩子的副作用。
权限决定按最严格的算:拒绝 > 延后 > 询问 > 允许。
排查
钩子不触发:
/hooks确认它挂在正确的事件下- 检查
matcher的大小写 - 手工测一下:
echo '{"tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh; echo $?
报「command not found」: 用绝对路径,或者用 ${CLAUDE_PROJECT_DIR} 开头,并确认给过执行权限。
看执行细节: 按 Ctrl + O 打开完整记录。成功的钩子什么都不显示;被拦的显示标准错误;出错的显示一行错误提示。
最后一条建议
别一上来就配一堆。
先配那个通知钩子。 它立刻就有用,而且不会出错。
然后等你真的遇到「它又忘了做某件事」的时候,再配第二个。
钩子是解决具体问题的工具,不是需要提前搭建的框架。
本节事实查证日期:2026-08-05。 依据:官方 Hooks guide 与 Hooks 参考页。