返回列表

Claude Code Hooks 完全指南:让自动化真正确定性执行

2026-03-14·7 分钟阅读·AI教程

前言

上一篇 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-commitpost-commit,Claude Code 有 PreToolUsePostToolUse

关键区别在于"确定性":

CLAUDE.mdHooks
本质自然语言指令Shell 命令
执行方式Claude 理解后"尽量"遵守系统自动执行,100% 触发
失败处理Claude 可能忽略非零退出码会阻断操作
适用场景编码风格、架构约定格式化、lint、安全拦截
类比员工手册门禁系统

经验法则: 如果一条规则"违反了后果很严重",用 Hook;如果是"最好遵守但偶尔例外也行",用 CLAUDE.md。


二、事件类型

Hooks 支持以下事件类型:

事件触发时机典型用途
PreToolUse工具调用之前拦截危险操作、参数校验、权限检查
PostToolUse工具调用之后自动格式化、lint、日志记录
NotificationClaude 发送通知时转发到 Slack、微信、系统通知
StopClaude 完成回复时自动跑测试、清理临时文件
SubagentStop子 Agent 完成时校验子 Agent 输出质量

最常用的是 PreToolUsePostToolUse,覆盖了 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 不生效时,排查步骤:

  1. 检查 JSON 语法 — settings.json 格式错误会导致整个配置失效
  2. 检查 matcher — 工具名大小写敏感,bashBash
  3. 手动测试命令 — 把 Hook 命令单独在终端跑一遍
  4. 检查 exit codeexit 2 才是阻断,exit 1 只是报错不阻断
  5. 查看 stderr — Hook 的 stderr 输出会显示在 Claude Code 中
# 手动测试 Hook 命令
echo '{"tool_name":"Bash","tool_input":"rm -rf /"}' | your-hook-command
echo $?  # 检查 exit code

8.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 自动化的核心机制。记住两个关键点:

  1. CLAUDE.md 是建议,Hooks 是保证 — 关键规则用 Hook 强制执行
  2. PreToolUse 拦截,PostToolUse 修复 — 危险操作在执行前拦截,代码质量在执行后修复

花 15 分钟配置一套 Hooks,能帮你在每次 Claude Code 会话中省去大量手动检查的时间。


推荐阅读