Claude Code Hooks 允许你在 Claude Code 运行的每个关键时机(调用工具前后、提交 prompt、想结束对话等)触发你预先配好的脚本,从而实现对 Claude Code 行为的精细控制。
不过说实话,目前模型的指令遵从能力已经很强了,hooks 更多的是一种能力扩展,大部分时候没必要使用,所以大家了解一下这个功能即可。
Claude Code 的常用技巧 里讲过用 CLAUDE.md 告诉 Claude 你的项目约定,让它「记住」该怎么干活。但 CLAUDE.md 终究是软提醒,Claude 还是会忘,还是会跳过你交代过的步骤,还是会拍着胸脯说「已完成」,但其实代码根本没跑通。
这篇讲的 hooks 可以一定程度上解决这个问题。在 Claude 的每个关键节点(如调用工具前后、提交 prompt、想结束对话时)都会触发你预先配好的脚本,脚本的输出/返回值可以影响 Claude Code 的下一步行动。
说白了,hooks 把那些「指望 Claude 自觉」的约定,变成了系统层面的强制。
hooks 写在 settings.json 里,跟权限设置在同一个文件。主要有两个位置:
- 用户级 :
~/.claude/settings.json,所有项目都生效,适合放跨项目通用的规则(比如拦危险命令)。 - 项目级 :项目根目录下的
.claude/settings.json,可以跟着项目走,团队共享。建议把跟具体项目相关的 hook(比如 git commit 规范等)放这里。
两个文件里的 hook 会同时生效,不会互相覆盖。
最小可用配置
先看一个完整最小例子,感受一下结构:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "echo 'Claude 改了一个文件!' >&2"
}
]
}
]
}
}
把这段加到 ~/.claude/settings.json,重启 Claude Code,让它改个文件,你会在它的输出里看到这条消息。
注意结构是两层嵌套 的 hooks:
- 外层
hooks的 key 是事件名(比如PostToolUse)。 - 内层是一个数组,每个元素是一组
{ matcher, hooks }。 matcher用来过滤工具名,匹配上才触发内层的hooks。- 每个具体 hook 都要写
type字段,告诉 Claude Code 这条 hook 该怎么执行。
type 字段官方支持 五种取值:
command:跑一条 shell 命令,用退出码或 stdout 控制后续行为,最常用http:把 hook 输入 POST 到一个 HTTP 端点mcp_tool:调用某个已连接的 MCP server 上的工具prompt:把判定条件交给一个 LLM 单轮评估,让它返回 JSON 决定放行还是拦截agent:起一个子 agent 来做验证
本文的例子全用 command,其他四种不展开。其中 prompt 类型比较特别,它不跑脚本而是让模型来判定条件,Claude Code 的 /goal 命令就是基于它实现的,原理可以看/goal 命令是怎么实现的。
同一个事件下可以配多个 hook,匹配上的会并行执行,不是串行短路。只要其中一个 hook 拦下动作,其他 hook 即便放行也救不回来,所以做安全拦截不用担心被别的 hook 绕过。
matcher 是带有通配符的字符串,主要用来匹配工具名,它的几种写法:
| matcher 值 | 含义 |
|---|---|
省略 / "*" / "" |
匹配所有 |
"Bash" |
精确匹配 Bash 工具 |
| `"Edit | Write"` |
"mcp__memory__.*" |
含其他字符时按 JS 正则匹配 |
要按 Bash 子命令过滤(比如只在 git commit 时触发)的话,不能用 matcher,要用单独的 if 字段:
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(git commit *)",
"command": "..."
}
]
}
每次改完 settings.json 都需要重启 Claude Code 才生效。
后面例子里所有脚本我会统一放在 .claude/hooks/ 目录下,但这只是约定,放别处也可以,配置里路径写对就行。
小技巧
嫌手写 settings.json 麻烦其实可以偷懒。直接在 Claude Code 里跟 Claude 说「帮我加一个 PostToolUse hook,每次写完文件跑代码格式化工具」之类的话,让它调用内置的 /update-config skill 查询相关的配置方法,然后帮你改配置文件,写完还会自动校验格式。
① 表面问题:这些配置和脚本到底在干什么?
你看到的是:
PreToolUse、matcher、command、stdin、exit 2......
它们其实分别回答几个很普通的问题:
什么时候做?检查哪种操作?运行哪个程序?给它什么信息?检查完怎么办?
文章一上来展示所有配置,就像还没讲清楚交通灯的作用,先让你看接线图。
② 实际问题:怎样让一条要求在操作发生时真正被执行?
假设你告诉 Claude:
"提交代码之前,先运行检查。"
这里有两件不同的事:
- 理解要求:模型读懂这句话,规划接下来做什么。
- 落实检查:软件在提交发生之前,确实运行检查,并处理结果。
CLAUDE.md 主要帮助模型理解和遵循约定。Hook 则可以把检查接到执行流程里。
理解这层区别,就能明白文章为什么说"给 Claude Code 加规矩"。
③ 拆开概念:到底是谁做什么?
| 组件 | 用普通话解释 |
|---|---|
| Claude 模型 | 思考下一步,提出"我要提交代码" |
| Claude Code | 承载模型、调度工具、运行 Hook 的软件 |
| 工具 | 真正执行操作的能力,例如运行终端命令、修改文件 |
| Hook 事件 | 软件预留的触发时机,例如"调用工具之前" |
| Hook 配置 | 指定在哪个时机、满足什么条件、运行什么 |
| Hook 脚本 | 实际检查或处理事情的小程序 |
对于文章里的 command 类型 Hook,触发检查的是 Claude Code;检查逻辑写在脚本里。 正常配置生效并匹配事件时,不需要模型每次主动想起来调用它。
所以,这三者的关系是:
模型提出操作 → Claude Code 调度 → 工具执行操作
↓
指定时机运行 Hook
④ 有哪些办法让"提交前检查"发生?
| 方案 | 怎样工作 | 优点 | 成本与局限 |
|---|---|---|---|
写进 CLAUDE.md |
模型读到要求后主动运行检查 | 简单,适合解释约定和工作方法 | 可能漏做,要核对执行记录 |
| 配置提交前 Hook | Claude Code 在匹配的操作前自动运行检查 | 检查可重复,也能阻止这次操作 | 需要维护脚本,会增加等待时间,配置错误可能漏拦或误拦 |
两者可以配合:用文字说明"为什么要检查",用 Hook 执行明确的检查条件。
规则文件和脚本都可以版本化、共享;但 Hook 会运行真实程序,所以要明确它能读写什么、是否访问外部服务。验证时应分别模拟"检查通过"和"检查失败",确认操作是否按预期继续。出现持续误拦或检查器故障时,应修正规则或停用对应 Hook。
⑤ 现在选择怎么理解?
我建议先理解 PreToolUse:工具执行前的检查关口。
它最容易说明 Hook 的核心:操作还没发生,所以检查结果能够决定是否让它发生。
自动格式化、注入上下文、结束前检查,都可以等这个流程弄懂以后再看。
⑥ 机制是什么?把一次提交慢放
假设模型准备通过终端工具执行:
git commit -m "修复登录问题"
流程如下:
```mermaid
flowchart TD
A[模型请求提交代码] --> B[Claude Code 暂缓执行工具]
B --> C[触发匹配的 PreToolUse Hook]
C --> D[脚本运行代码检查]
D --> E{检查通过了吗}
E -->|通过| F[Hook 不阻拦,继续权限与执行流程]
E -->|失败| G[阻止本次工具调用,把错误反馈给模型]
G --> H[模型根据反馈尝试修复]
```
这里最关键的是:检查失败时,这次提交命令还没有执行。
再把配置翻译成人话:
PreToolUse
→ 在工具执行之前
matcher: Bash
→ 关注运行终端命令的工具
if: Bash(git commit *)
→ 进一步筛选出提交代码的操作
type: command
→ 用运行本地命令的方式执行 Hook
command: 某个检查脚本
→ 真正运行这个脚本
脚本怎样知道"现在准备做什么"?Claude Code 会通过 stdin,也就是程序的输入通道,给它一份 JSON 数据:
{
"tool_name": "Bash",
"tool_input": {
"command": "git commit -m \"修复登录问题\""
}
}
JSON 在这里就是一张结构化的"操作说明单"。
脚本检查后,再返回处理结果。对于这个前置检查的简单用法:
exit 0,且没有额外拒绝指令:脚本执行成功,这道 Hook 不阻拦。exit 2:阻止本次操作。stderr:错误信息通道,可以把"哪项检查失败"反馈给模型。
注意,Hook 不阻拦,不代表所有权限都自动放行,也不代表命令一定执行成功。
失败反馈能帮助模型修复,但不能保证模型一定修得好。遇到缺少依赖、外部服务故障等问题,需要明确报告原因,避免无限重试。
文章剩下的例子,只是在换触发位置和要做的事:
| 时机 | 可以安排什么 |
|---|---|
PreToolUse:工具执行前 |
检查,不符合条件就拦截 |
PostToolUse:工具成功执行后 |
格式化刚改好的文件;不能阻止已经发生的修改 |
UserPromptSubmit:用户提交消息后、模型处理前 |
补充当前分支等信息 |
Stop:模型准备结束这一轮时 |
检查是否还有需要继续处理的事项 |
SessionStart:会话开始或恢复时 |
准备会话需要的上下文 |
⑦ 同一领域的类比:网页按钮的点击事件
网页可以配置:
用户点击"保存" → 自动执行保存函数
Claude Code 可以配置:
模型准备调用工具 → 自动执行检查脚本
它们共同的机制都是:先登记"发生什么时执行什么",等事件发生后,由软件触发。
⑧ 生活里的类比:出厂质检
可以把模型想成准备发货的工作人员:
CLAUDE.md:工作手册写着"发货前检查"。PreToolUse:货物离厂前的检查站。- Hook 脚本:具体的质检程序。
- 检查结果:放行或退回,并说明原因。
但质检站只能检查你规定的项目。只检查重量,不能证明颜色、尺寸也正确。
同样,测试通过只能证明已检查的条件通过,不能证明整个需求已经完成。 原文把 Hook 描述成让模型无法"假装完成",说得过满了。
⑨ 证据、边界和下一步
我已对照你贴的文章与 Claude Code 官方 Hooks 文档:事件触发、脚本接收输入、前置 Hook 阻止工具调用,这条核心机制成立。但原文部分细节存在版本差异,例如"exit 2 时忽略所有 JSON 输出"已不符合当前文档,配置时需要查对应版本。