前言
在上一篇 CLAUDE.md 指南里,我提到 CLAUDE.md 告诉 Claude "做什么",settings.json 告诉 Claude "怎么做"。而在进阶指南中,我也简单展示了几个 Hook 示例。
但 Hooks 值得单独拿出来讲。因为它解决了一个根本问题:CLAUDE.md 是建议性的,Claude 可能不遵守;Hooks 是确定性的,一定会执行。
这篇文章把 Hooks 讲透。
一、Hooks 是什么
一句话:Hooks 是在 Claude Code 生命周期特定节点自动执行的 Shell 命令。
你可以把它理解为 Git Hooks 的 Claude Code 版本。Git 有 pre-commit、post-commit,Claude Code 有 PreToolUse、PostToolUse。
关键区别在于"确定性":
| CLAUDE.md | Hooks | |
|---|---|---|
| 本质 | 自然语言指令 | Shell 命令 |
| 执行方式 | Claude 理解后"尽量"遵守 | 系统自动执行,100% 触发 |
| 失败处理 | Claude 可能忽略 | 非零退出码会阻断操作 |
| 适用场景 | 编码风格、架构约定 | 格式化、lint、安全拦截 |
| 类比 | 员工手册 | 门禁系统 |
经验法则: 如果一条规则"违反了后果很严重",用 Hook;如果是"最好遵守但偶尔例外也行",用 CLAUDE.md。
二、事件类型
Hooks 支持以下事件类型:
| 事件 | 触发时机 | 典型用途 |
|---|---|---|
PreToolUse | 工具调用之前 | 拦截危险操作、参数校验、权限检查 |
PostToolUse | 工具调用之后 | 自动格式化、lint、日志记录 |
Notification | Claude 发送通知时 | 转发到 Slack、微信、系统通知 |
Stop | Claude 完成回复时 | 自动跑测试、清理临时文件 |
SubagentStop | 子 Agent 完成时 | 校验子 Agent 输出质量 |
最常用的是 PreToolUse 和 PostToolUse,覆盖了 90% 的场景。
三、配置方式
3.1 交互式配置
最简单的方式,在 Claude Code 中输入:
/hooks会打开交互式 UI,引导你选择事件类型、匹配规则和要执行的命令。适合快速添加单个 Hook。
3.2 手动编辑 settings.json
更灵活的方式,直接编辑配置文件:
~/.claude/settings.json → 全局 Hooks(所有项目生效)
项目根目录/.claude/settings.json → 项目级 Hooks(团队共享)
JSON 结构:
{
"hooks": {
"事件类型": [
{
"matcher": "匹配规则",
"command": "要执行的 Shell 命令"
}
]
}
}完整示例:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"command": "echo '检查命令安全性...'"
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"command": "npx prettier --write \"$CLAUDE_FILE_PATH\""
}
]
}
}3.3 配置层级
和 CLAUDE.md 一样,Hooks 也有层级:
~/.claude/settings.json → 全局(个人偏好)
.claude/settings.json → 项目级(团队共享)
两层的 Hooks 会合并执行,不是覆盖。也就是说,全局和项目级的 Hook 都会触发。
四、Matcher 匹配规则
Matcher 决定了 Hook 在哪些工具调用时触发。
4.1 匹配方式
| 方式 | 语法 | 示例 |
|---|---|---|
| 精确匹配 | 工具名 | "Bash" |
| 多选匹配 | 用 | 分隔 | "Edit|Write" |
| 空/不写 | 匹配所有工具 | "" 或省略 matcher |
4.2 常见工具名
| 工具名 | 说明 |
|---|---|
Bash | 执行 Shell 命令 |
Edit | 编辑文件(部分修改) |
Write | 写入文件(完整覆盖) |
Read | 读取文件 |
Glob | 文件模式搜索 |
Grep | 内容搜索 |
WebFetch | 网络请求 |
WebSearch | 网络搜索 |
4.3 匹配技巧
// 匹配所有文件写入操作
"matcher": "Edit|Write"
// 只匹配 Shell 命令
"matcher": "Bash"
// 匹配所有工具(省略 matcher 或留空)
"matcher": ""五、Hook 的输入输出
这是 Hooks 最强大的部分——Hook 不只是"执行一个命令",它可以接收上下文信息,也可以返回决策结果。
5.1 输入(stdin)
每个 Hook 执行时,会通过 stdin 收到一个 JSON 对象:
{
"session_id": "abc123",
"tool_name": "Edit",
"tool_input": {
"file_path": "/src/app.tsx",
"old_string": "...",
"new_string": "..."
}
}你可以在 Hook 命令中读取这个 JSON 来做条件判断。
5.2 输出(exit code)
| Exit Code | 含义 | 效果 |
|---|---|---|
0 | 通过 | 操作继续执行 |
2 | 阻断 | 操作被取消,stdout 内容作为原因展示 |
| 其他非零 | 错误 | Hook 本身出错,操作继续(不阻断) |
5.3 输出(stdout JSON)
除了 exit code,Hook 还可以通过 stdout 返回 JSON 来精确控制行为:
{
"decision": "block",
"reason": "检测到 rm -rf 命令,已拦截"
}{
"decision": "approve"
}5.4 完整流程
Claude 要调用工具
↓
系统检查 PreToolUse Hooks
↓
匹配的 Hook 收到 stdin JSON
↓
Hook 执行命令,返回 exit code + stdout
↓
exit 0 → 继续执行工具
exit 2 → 阻断,展示原因
↓
工具执行完成
↓
系统检查 PostToolUse Hooks
↓
匹配的 Hook 执行(格式化、lint 等)
六、实战示例一:通用开发
6.1 保存后自动格式化
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"command": "npx prettier --write \"$CLAUDE_FILE_PATH\""
}
]
}
}每次 Claude 编辑或创建文件后,自动用 Prettier 格式化。再也不用在 CLAUDE.md 里写"请用 Prettier 格式化"了。
6.2 拦截危险命令
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -qE 'rm -rf|git push --force|git reset --hard|drop table|DROP TABLE'; then echo '{\"decision\": \"block\", \"reason\": \"危险操作被拦截:检测到破坏性命令\"}'; exit 2; fi"
}
]
}
}这比在 CLAUDE.md 里写"不要执行危险命令"可靠得多——CLAUDE.md 是建议,Hook 是门禁。
6.3 编辑后自动 lint
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"command": "ext=\"${CLAUDE_FILE_PATH##*.}\"; if [ \"$ext\" = \"ts\" ] || [ \"$ext\" = \"tsx\" ]; then npx eslint --fix \"$CLAUDE_FILE_PATH\" 2>/dev/null; fi"
}
]
}
}只对 TypeScript 文件执行 ESLint 自动修复。
6.4 任务完成时发送通知
{
"hooks": {
"Notification": [
{
"matcher": "",
"command": "osascript -e 'display notification \"$CLAUDE_NOTIFICATION\" with title \"Claude Code\"'"
}
]
}
}macOS 上弹出系统通知。Windows 可以用 PowerShell 的 New-BurntToastNotification,Linux 可以用 notify-send。
七、实战示例二:Flutter 开发
Flutter 项目有自己的工具链,Hooks 可以完美集成。以下是一套完整的 Flutter 开发 Hooks 配置:
7.1 完整配置
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -qE 'setState\\('; then echo '{\"decision\": \"block\", \"reason\": \"禁止使用 setState,请使用 Riverpod 进行状态管理\"}'; exit 2; fi"
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"command": "if echo \"$CLAUDE_FILE_PATH\" | grep -qE '\\.dart$'; then dart format \"$CLAUDE_FILE_PATH\"; fi"
},
{
"matcher": "Edit|Write",
"command": "if echo \"$CLAUDE_FILE_PATH\" | grep -qE '\\.dart$'; then dart analyze \"$CLAUDE_FILE_PATH\" 2>&1 | head -20; fi"
}
],
"Stop": [
{
"matcher": "",
"command": "flutter test --reporter compact 2>&1 | tail -5"
}
]
}
}7.2 逐条解析
拦截 setState 使用:
{
"matcher": "Bash",
"command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -qE 'setState\\('; then echo '{\"decision\": \"block\", \"reason\": \"禁止使用 setState,请使用 Riverpod 进行状态管理\"}'; exit 2; fi"
}如果你的项目统一用 Riverpod,这个 Hook 能从源头拦截 setState。比 CLAUDE.md 里写"不要用 setState"强制力高得多。
Dart 文件自动格式化:
{
"matcher": "Edit|Write",
"command": "if echo \"$CLAUDE_FILE_PATH\" | grep -qE '\\.dart$'; then dart format \"$CLAUDE_FILE_PATH\"; fi"
}每次编辑 .dart 文件后自动执行 dart format,保证代码风格一致。
Dart 文件自动静态分析:
{
"matcher": "Edit|Write",
"command": "if echo \"$CLAUDE_FILE_PATH\" | grep -qE '\\.dart$'; then dart analyze \"$CLAUDE_FILE_PATH\" 2>&1 | head -20; fi"
}编辑后立即跑 dart analyze,问题在写入时就能发现,不用等到构建阶段。
回复结束后自动跑测试:
{
"matcher": "",
"command": "flutter test --reporter compact 2>&1 | tail -5"
}Claude 每次完成回复后自动跑测试,确保改动没有破坏现有功能。tail -5 只显示最后几行摘要。
7.3 Flutter Hooks 配合 CLAUDE.md
Hooks 和 CLAUDE.md 配合使用效果最好:
CLAUDE.md Hooks
───────────────────────────── ─────────────────────────────
"使用 Riverpod,不要用 setState" → 拦截 setState(强制执行)
"代码风格遵循 dart format" → 自动执行 dart format(保证一致)
"提交前确保 analyze 通过" → 自动执行 dart analyze(即时反馈)
"所有改动必须通过测试" → 自动执行 flutter test(持续验证)
CLAUDE.md 告诉 Claude 为什么这样做,Hooks 确保它一定这样做。
八、进阶技巧
8.1 Hook 类型
settings.json 中的 Hook 有三种类型:
| 类型 | 说明 |
|---|---|
command | 执行 Shell 命令(默认,最常用) |
prompt | 向 Claude 注入额外提示词 |
agent | 启动子 Agent 处理 |
大多数场景用 command 就够了。prompt 类型适合在特定操作前给 Claude 额外上下文。
8.2 环境变量
Hook 命令中可以使用以下环境变量:
| 变量 | 说明 | 可用事件 |
|---|---|---|
$CLAUDE_FILE_PATH | 被操作的文件路径 | PostToolUse (Edit/Write) |
$CLAUDE_TOOL_INPUT | 工具的输入参数(JSON) | PreToolUse |
$CLAUDE_NOTIFICATION | 通知内容 | Notification |
$CLAUDE_SESSION_ID | 当前会话 ID | 所有事件 |
8.3 调试 Hooks
Hook 不生效时,排查步骤:
- 检查 JSON 语法 — settings.json 格式错误会导致整个配置失效
- 检查 matcher — 工具名大小写敏感,
bash≠Bash - 手动测试命令 — 把 Hook 命令单独在终端跑一遍
- 检查 exit code —
exit 2才是阻断,exit 1只是报错不阻断 - 查看 stderr — Hook 的 stderr 输出会显示在 Claude Code 中
# 手动测试 Hook 命令
echo '{"tool_name":"Bash","tool_input":"rm -rf /"}' | your-hook-command
echo $? # 检查 exit code8.4 性能考虑
- Hook 是同步执行的,会阻塞 Claude 的操作
- 避免在 Hook 中执行耗时操作(如完整的
flutter test) PostToolUse中的格式化通常很快(< 1 秒),可以放心用Stop事件适合放稍微耗时的操作(如跑测试),因为 Claude 已经完成回复
九、常见问题
Q: Hook 和 CLAUDE.md 应该怎么配合? A: CLAUDE.md 管"软规则"(编码风格、架构偏好),Hooks 管"硬规则"(格式化、安全拦截)。两者互补,不是替代关系。
Q: Hook 执行失败会怎样?
A: 取决于 exit code。exit 2 会阻断操作;exit 1 或其他非零值只是报错,操作继续执行。这个设计是为了防止 Hook 本身的 bug 影响正常工作流。
Q: 可以在 Hook 中修改 Claude 的工具输入吗?
A: 不能直接修改输入参数。但 PreToolUse Hook 可以通过 exit 2 阻断操作,然后 Claude 会根据阻断原因调整行为。
Q: 多个 Hook 匹配同一个事件会怎样?
A: 按配置顺序依次执行。任何一个 Hook 返回 exit 2,操作就会被阻断。
Q: Hook 命令中可以用 Node.js 脚本吗? A: 当然可以。Hook 命令就是普通的 Shell 命令,你可以调用任何可执行文件:
{
"command": "node .claude/hooks/check-security.js"
}把复杂逻辑写在脚本文件里,Hook 命令只负责调用,这样更好维护。
Q: 团队成员不用 Claude Code 怎么办?
A: .claude/settings.json 只影响 Claude Code 用户,对其他人完全透明。可以放心提交到 Git。
总结
Hooks 是 Claude Code 自动化的核心机制。记住两个关键点:
- CLAUDE.md 是建议,Hooks 是保证 — 关键规则用 Hook 强制执行
- PreToolUse 拦截,PostToolUse 修复 — 危险操作在执行前拦截,代码质量在执行后修复
花 15 分钟配置一套 Hooks,能帮你在每次 Claude Code 会话中省去大量手动检查的时间。
推荐阅读
- CLAUDE.md 完全指南 — 本博客的 CLAUDE.md 深度解析
- Claude Code 进阶指南 — Claude Code 进阶技巧总结
- Claude Code Hooks 官方文档 — 官方的 Hooks 说明