10 · 怎么让操作 100% 执行
Hooks 不是提醒 Claude Code 记得做事,而是在工具调用、提示提交、停止前后设置确定性检查点。能容忍遗漏写 CLAUDE.md,不能容忍就用 Hook。
翔宇有一条工程习惯:凡是“忘一次就会出事”的规则,不要只写进提示词里。提示词让 AI 尽量记住,Hooks(钩子)让系统在关键节点强制检查。前者是提醒,后者是门禁。——翔宇
这一篇用 14 分钟换什么:第 9 篇讲 MCP 让 Claude Code 够到外部系统。够得到以后,风险也变大了。这一篇讲 Hooks:怎么在 Claude Code 准备调用工具、调用之后、提交提示、准备停止等时间点插入自动检查,让“必须发生的事”不再只靠 Claude 记得。
1. 先从一个危险的小动作开始
你在 CLAUDE.md 里写了一条规则:
永远不要修改 `.env` 文件。这条规则有用吗?有用。Claude Code 会读到,也大概率遵守。
但“大概率”不是“保证”。
想象某一次任务里,上下文很长、文件很多、你又让它“直接修好本地配置”。Claude 准备写 .env。如果只靠 CLAUDE.md,它可能会想起规则,也可能在复杂上下文里漏掉。
而 .env 不是普通文件。改坏一次,可能就是数据库连接、生产 token、第三方密钥出问题。
这时你不应该再问“怎么让提示词写得更严厉”。你应该换一个机制。
第一性原理:Hooks 解决的不是“Claude 不理解规则”,而是“某些规则不能靠模型记忆来执行”。
Claude Code 官方 Hooks guide 对 Hooks 的定位很清楚:Hooks 会在 Claude Code 生命周期的特定点自动运行,用来 enforce project rules、automate repetitive tasks、integrate with existing tools。也就是说,它是确定性自动化,不是又一段劝 Claude 听话的提示词。
2. CLAUDE.md、Permissions、Hooks 各管什么
先把三个概念摆清楚。
| 机制 | 中文理解 | 强度 | 适合什么 |
|---|---|---|---|
CLAUDE.md | 工作说明书 | 建议 / 习惯 | 风格、流程、偏好、项目背景 |
| Permissions | 权限闸门 | 允许 / 询问 / 拒绝 | 工具级访问控制 |
| Hooks | 自动检查点 | 到点必触发 | 格式化、审计、阻止危险动作、补上下文 |
举一个 TypeScript 项目:
- “优先用现有 service 层,不要新造抽象”适合写
CLAUDE.md。 - “Bash 里运行
rm -rf要询问”适合权限规则。 - “每次改
.ts文件后跑 formatter”适合 PostToolUse Hook。 - “任何写
.env的尝试都拦下来”适合 PreToolUse Hook。
这三者不是互相替代。
CLAUDE.md 给方向,Permissions 管门槛,Hooks 做检查点。
一句话理解:能容忍偶尔遗漏的,写进 CLAUDE.md;需要工具级边界的,用 Permissions;到某个事件点必须自动发生的,用 Hooks。
3. Hook 其实是生命周期检查点
Hook 的本质是:Claude Code 运行到某个时间点,自动把一段 JSON 输入交给你配置的 handler(处理器)。handler 可以是命令、HTTP 端点、MCP 工具、prompt 或 agent。
先看一个简化时间线:
flowchart LR
Prompt["用户提交提示"]
Think["Claude 思考"]
Pre["PreToolUse<br/>工具执行前"]
Tool["工具调用<br/>Edit / Write / Bash / MCP"]
Post["PostToolUse<br/>工具成功后"]
Stop["Stop<br/>准备停止"]
Prompt --> Think --> Pre --> Tool --> Post --> Think --> Stop
style Pre fill:#fef3c7,stroke:#f59e0b,stroke-width:2px
style Post fill:#dcfce7,stroke:#22c55e,stroke-width:2px
style Stop fill:#f3e8ff,stroke:#a855f7,stroke-width:2px
Hooks 不是只在“写文件前”能用。官方 Hooks reference 列了很多事件,比如:
UserPromptSubmit:用户提示提交时触发,适合注入上下文、拦截危险请求。PreToolUse:工具执行前触发,适合阻止危险命令、改写工具输入。PermissionRequest:即将弹权限请求时触发,适合自动允许/拒绝某类请求。PostToolUse:工具成功后触发,适合格式化、审计、补充反馈。PostToolUseFailure:工具失败后触发,适合记录失败、给 Claude 修复线索。Stop:Claude 准备停下时触发,适合检查任务是否真的完成。SubagentStop:子代理准备结束时触发,适合审查子代理结果。PreCompact/PostCompact:上下文压缩前后触发,适合归档或重新注入关键上下文。Elicitation/ElicitationResult:MCP server 请求用户输入前后触发,适合审计或自动处理信息征询。
新手不需要一口气记完。先记三个最常用的:
PreToolUse:动手前拦。PostToolUse:动手后补。Stop:收工前查。
Hook 触发顺序到底是什么样:一次完整工具调用的事件链是 UserPromptSubmit → (思考) → PreToolUse → (Permission 检查) → 工具执行 → PostToolUse / PostToolUseFailure → (思考) → Stop。关键事实:① PreToolUse 在 Permission 检查之前触发——Hook 可以提前拦掉危险操作不弹权限窗;② Stop 在准备结束本轮之前触发——可以阻止 Claude 过早收工。所以 Hook 不是"事后审计",是主动嵌入决策链路——这是它跟"日志"的本质区别。
新手坑:不要为了“控制感”给每个事件都挂 Hook。Hook 越多,运行越慢,干扰越多,排查也越难。
4. 一个最小 Hook 长什么样
Hooks 写在 settings JSON 里。结构有三层:
- 选事件,比如
PreToolUse。 - 选 matcher group,比如只匹配
Write或Bash。 - 放一个或多个 hook handler。
比如阻止写 .env:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/protect-env.sh"
}
]
}
]
}
}脚本大概这样:
#!/usr/bin/env bash
set -euo pipefail
INPUT="$(cat)"
FILE_PATH="$(printf '%s' "$INPUT" | jq -r '.tool_input.file_path // empty')"
case "$FILE_PATH" in
*.env|*/.env|*.env.*)
echo "Blocked: environment files may contain secrets and must be edited manually." >&2
exit 2
;;
esac
exit 0Claude Code 会把事件 JSON 通过 stdin 传给 command hook。上面脚本读 tool_input.file_path,如果目标是环境文件,就返回 exit code 2。
关键点:Hook 不需要说服 Claude。它在工具执行前已经拿到了结构化输入,可以直接基于文件路径、命令、权限模式做判断。
5. exit code 2 不只是“报错”
官方 Hooks reference 明确说:exit 0 表示成功;exit 2 表示 blocking error;其他退出码对多数事件来说是 non-blocking error。
这很重要:
exit 0:成功,动作继续;stdout 里的 JSON 可能被解析。exit 2:阻止;stderr 会反馈给 Claude。exit 1或其他:多数事件下只是非阻塞错误,动作继续。
所以不要用传统直觉写:
exit 1如果你想阻止操作,要写:
echo "Blocked: do not edit .env" >&2
exit 2并且记住一个细节:exit 2 时,Claude Code 忽略 stdout 和 stdout 里的 JSON,只看 stderr 作为反馈。 如果你要用 JSON 做更细控制,通常是 exit 0 然后在 stdout 输出 JSON。
例如 PreToolUse 可以返回:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Database writes are not allowed."
}
}实操判断:简单阻止用 stderr + exit 2;需要更细的 allow / deny / ask / 修改输入,用 exit 0 + JSON output。
6. 五种 handler:不只有 shell
旧理解里,Hook 常被理解成“自动跑一个 shell 脚本”。现在已经不止这样。
官方 Hooks reference 里的 handler type 有五类:
command:执行 shell 命令,适合格式化、文件保护、日志。http:POST 到一个 URL,适合通知、审计、接外部系统。mcp_tool:调已连接 MCP server 的工具,适合复用现有 MCP 能力。prompt:让 Claude 模型做一次判断,适合检查是否真的完成、是否符合复杂规则。agent:启动带工具的 verifier agent,适合需要读文件、搜索后再判断的场景。
这五种放在一条光谱上看:
确定性强 -> 判断力强
command -> http -> mcp_tool -> prompt -> agent例子:
- 改
.ts后跑 Prettier:command。 - 每次权限请求发审计系统:
http。 - 复用一个内部 policy MCP server:
mcp_tool。 - Stop 前判断“任务是否真的完成”:
prompt。 - Stop 前让一个 verifier agent 搜索测试结果、读 diff 再判断:
agent。
不要反过来用重武器:能用 command 判断的,就不要用 prompt;能用 prompt 判断的,不一定要开 agent。判断越智能,成本和不确定性也越高。
为什么 Anthropic 设计 5 种 handler 而不是只给 command:用 shell command 解决一切是最简单——但有些判断 shell 写不出来。"任务真的完成了吗"需要语义理解,正则做不到——这就是 prompt handler 的位置。"任务完成了但代码风格还有 3 处问题"需要读代码 + 跑命令——这就是 agent handler。5 种递进的设计哲学是:让你按"判断复杂度"选合适的工具,不强迫所有规则都在 shell 里写——避免出现"写 100 行 shell 模拟模型推理"的反模式。
7. Block-at-Write 还是 Block-at-Submit
Hook 很容易被用重。
比如你想保证测试通过。你可以在每次 Edit 后立刻跑全量测试吗?技术上可以。但这会让 Claude 每改一个文件就等很久,任务体验会崩。
更好的方式是区分两类规则:
- Block-at-Write:动手前或刚动手后立即拦,适合改
.env、删库、写生产配置。 - Block-at-Submit:收工前统一检查,适合测试、lint、文档完整性、验收清单。
危险动作要早拦,因为一旦发生就可能造成损失。
质量检查可以晚一点,因为 Claude 需要先完成一组相关修改,再统一修 lint、测试和格式。
Stop hook 就适合 Block-at-Submit:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "复盘本轮结果。如果还缺测试或必要的验证步骤,就阻止停止:$ARGUMENTS"
}
]
}
]
}
}底层逻辑:早拦保护不可逆风险,晚查保护交付质量。不要把所有质量规则都塞到每一次文件写入前。
8. Hook 配置放在哪里
Hook 遵守 Claude Code settings scope:
~/.claude/settings.json:作用于你所有项目,适合个人通知、个人日志。.claude/settings.json:作用于当前仓库所有协作者,适合团队规范、安全规则。.claude/settings.local.json:只作用于当前仓库你自己,适合本机实验、临时调试。- plugin:作用于插件启用处,适合插件随带的 Hook。
- managed settings:作用于组织级,适合安全和合规强制策略。
官方配置文档里,scope 优先级大致是:managed 最高,其次命令行参数、local、project、user。项目级能覆盖用户级,managed 不能被普通用户覆盖。
你可以用:
/hooks打开只读 hooks 菜单,检查当前有哪些 hook、来自哪个来源、匹配什么事件。官方说明里 /hooks 是 read-only:想改配置,还是要编辑 settings JSON 或让 Claude 修改文件。
团队规则别放错:团队必须共同遵守的 Hook 放 .claude/settings.json;只对你本机有意义的通知、路径和实验,放 .claude/settings.local.json 或用户级。
9. 幂等性和性能
Hooks 是代码。代码就有工程质量。
第一条是幂等性。
幂等性就是:跑一次和跑多次,结果不应该越来越坏。
好的 Hook:
格式化文件
检查文件路径
追加带唯一 id 的审计日志危险 Hook:
每触发一次就插一条数据库记录
每触发一次就覆盖一个全局配置
每触发一次就发一条无法去重的外部通知Claude Code 可能多次编辑同一个文件,PostToolUse 就可能多次触发。如果 Hook 没有幂等性,就会制造重复副作用。
第二条是性能。
PreToolUse 是热路径。你在每次工具调用前跑一个 10 秒脚本,Claude Code 体验会立刻变慢。
减少成本的方法:
- 用
matcher限定工具名。 - 用
if进一步限定命令或文件类型。 - 小检查用 command,不要动不动 prompt/agent。
- 长任务考虑 async,但不要用 async 做必须同步拦截的安全规则。
Hook 设计原则:越靠前、越高频的事件,Hook 越要短、准、可预测。
10. 这一篇和前几篇怎么连起来
到这里,Claude Code 的控制层可以串成一张图。
flowchart TB
Need["你想控制 Claude Code 行为"]
Know["长期知识和偏好<br/>CLAUDE.md"]
Reuse["重复任务流程<br/>Skills"]
Reach["连接外部系统<br/>MCP"]
People["隔离或协作<br/>SubAgents / Agent Teams"]
Guard["确定性检查点<br/>Hooks"]
Perm["工具访问边界<br/>Permissions"]
Need --> Know
Need --> Reuse
Need --> Reach
Need --> People
Need --> Guard
Need --> Perm
style Guard fill:#fef3c7,stroke:#f59e0b,stroke-width:2px
style Perm fill:#fee2e2,stroke:#ef4444,stroke-width:2px
每一层解决的问题不同:
CLAUDE.md:让 Claude 知道项目规则,关键词是 should。- Skills:复用任务流程,关键词是 reusable workflow。
- SubAgents:隔离旁支任务,关键词是 separate context。
- Agent Teams:多会话协作,关键词是 shared task list。
- MCP:够到外部系统,关键词是 external tools。
- Hooks:到点自动检查,关键词是 deterministic checkpoint。
- Permissions:哪些工具能用,关键词是 access control。
第 11 篇会专门讲 Permissions。先记住边界:Hook 可以在事件点做检查,Permissions 决定工具访问规则。两者经常一起用,但不是一回事。
一句话理解:Hooks 是“到点必查”的检查点;Permissions 是“能不能用工具”的访问边界;CLAUDE.md 是“希望你怎么做”的说明书。
11. 本章自检
试着用自己的话回答:
- 为什么"我已经写进 CLAUDE.md"不能等同于"这件事一定会发生"?对应 §1-§2。
- exit 2 和 exit 1 在 Hooks 里有什么关键区别?如果你想阻止写
.env,应该用哪个?对应 §5。 - 动手题 ⭐:在你项目根新建
.claude/hooks/protect-secrets.sh,写一个 PreToolUse Hook:当 Claude 想 Edit / Write 任何文件名包含.env/secret/credentials的文件时,用 stderr 输出"危险文件不允许编辑"+ exit 2 阻止。然后在.claude/settings.json注册。自检:手动让 Claude 试着改.env,看 Hook 有没有拦下来 + 看 stderr 信息有没有进 Claude 上下文。这一道题做完你会知道:CLAUDE.md 提醒 ≠ 真拦得住,Hook 才是确定性。对应 §1 + §4。
过关标准:能用一句话说清:Hooks 是 Claude Code 生命周期里的确定性检查点,用来自动执行、阻止或反馈那些不能只靠模型记忆完成的规则。
本篇术语速查表
- Hook:钩子,在 Claude Code 生命周期特定点自动执行的处理器。
- handler:处理器,Hook 触发后真正运行的 command、http、prompt 等。
- matcher:匹配器,限定 Hook 对哪些工具或事件生效。
PreToolUse:工具执行前,工具调用前触发,可阻止危险动作。PostToolUse:工具执行后,工具成功后触发,常用于格式化和审计。Stop:停止前,Claude 准备结束当前回合时触发。- exit code:退出码,脚本结束时返回给 Claude Code 的状态数字。
- stderr:标准错误输出,exit 2 时反馈给 Claude 的错误信息来源。
- idempotency:幂等性,多次执行结果仍然安全稳定。
- Permissions:权限系统,控制 Claude Code 工具访问边界的机制。
官方资料
接下来去哪
11 · 权限怎么管
Hooks 是检查点,Permissions 是访问边界。下一篇拆 Claude Code 怎么决定某个工具能不能用、要不要问你。
09 · 怎么连外部服务(上一篇)
MCP 让 Claude Code 够到外部系统。Hooks 帮你在这些外部操作前后加检查和审计。
03 · 怎么记住你的习惯
分不清 CLAUDE.md 和 Hooks 时,回到记忆篇:长期习惯写说明,强制检查写自动化。
如果只记一个判断:凡是“漏一次就会出事”的规则,不要只写成提醒,要放到 Hook 或 Permission 里。