Vibe Coding 01:Claude Code Hooks —— 让 AI 按你的规矩来

Claude Code Hooks 实践指南。覆盖 PostToolUse 自动格式化、PreToolUse 命令拦截、Notification 桌面提醒、Stop 审查门禁等场景的配置和实测经验。

用了几个月 Claude Code,我发现一个规律:每次让 AI 自由发挥,总有那么一两件小事它会忘。格式化不一致、改到不该动的文件、跑完命令不通知我。

这些东西不该靠 prompt 去”请”它做——prompt 是软约束,它可能在第七轮对话后开始忽略。Hooks 是硬约束,每次都执行。

Claude Code 的 Hooks 在特定生命周期节点自动跑 shell 命令。编辑文件后、执行命令前、等待用户输入时、会话结束时——每个节点都能挂一段脚本。

我不是要翻译官方文档。这篇只记录我实际用上的几个 Hook,以及踩到的坑。

Hook 放在哪

两个位置:

位置作用范围是否提交 Git
~/.claude/settings.json所有项目否,本机
.claude/settings.json当前项目

我个人的习惯:格式化、通知这类通用 Hook 放 ~/.claude/settings.json。项目规则类(拦截危险命令、代码审查门禁)放项目的 .claude/settings.json,跟项目一起提交。

验证配置用 /hooks 命令,会列出所有已注册的 Hook 及触发次数。只读的,要改得回到 settings.json。

第一个 Hook:编辑后自动格式化

这是最直观的用法。Claude Code 编辑完文件后自动跑 prettier,代码风格永远一致,不需要在 prompt 里反复强调。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

注意这个 matcher: "Edit|Write"。不加的话每次工具调用完都跑,包括 Bash、Read 之类的——没必要。Edit|Write 只匹配文件编辑操作。

有个坑:Claude 也可以通过 Bash 工具用 echo > filesed -i 改文件,PostToolUse 匹配不到这种。如果你需要覆盖所有文件变更,加一个 Stop Hook 在每轮结束扫描 git status --porcelain。不过我试下来,Edit/Write 覆盖了 95% 的场景,剩下的不值得多跑一个扫描。

拦截危险命令:PreToolUse

这是我最刚需的 Hook。已经发生过两次 Claude 顺手改了 .env 文件——不是故意的,但改了就是改了。

在项目根目录的 .claude/settings.json 加:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}

脚本 protect-files.sh

#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")

for pattern in "${PROTECTED_PATTERNS[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
    exit 2
  fi
done

exit 0

关键点:

  • exit 2 阻止操作。Claude 会收到你写在 stderr 里的原因,所以它能根据反馈调整方向。
  • exit 0 放行。不需要决策时保持安静。
  • 脚本要 chmod +x,不然跑不起来。

用了几周,.env 再也没被改过。Claude 被拦截后会自动换个方式——比如建议你自己手动改,或者问你要不要跳过保护。

桌面通知:切换窗口不用盯着终端

Claude Code 跑长时间任务时,我会切到别的窗口。但不知道它什么时候干完。Notification Hook 解决这个问题。

macOS 用的命令:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}

空的 matcher 对所有通知类型触发——权限请求、空闲等待、身份认证完成都弹通知。想更精确的话可以设成 idle_prompt,只在等用户输入时提醒。

macOS 上有个坑:osascript 的通知需要 Script Editor 有通知权限。第一次跑不会自动弹出权限请求,它静默失败。解决方法:先手动在终端跑一次 osascript -e 'display notification "test"',然后去 系统设置 > 通知 找到 Script Editor 并打开允许通知。之后再跑一次确认能看到通知弹窗。

Linux 用 notify-send,Windows 用 PowerShell。我在三台机器上都配了,很稳。

Stop Hook:每轮结果自动审查

这个比较进阶。Stop Hook 在 Claude 完成回复后触发。我用来做两件事:

1. 自动代码审查。 跑 lint、typecheck,不通过不允许结束。用的是 type: "agent",因为它要读文件、跑命令。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "agent",
            "prompt": "Review the code changes in this session. Check for: 1) unhandled errors, 2) security issues, 3) broken imports. If issues found, respond with {\"ok\": false, \"reason\": \"具体问题描述\"}. If clean, respond with {\"ok\": true}.",
            "timeout": 120
          }
        ]
      }
    ]
  }
}

"ok": false 时 Claude 会继续工作,用 reason 里的描述作为下一轮指令。

说实话,这个 Hook 在日常小任务上有点重——每改两行代码都要跑一次审查。我目前只在重要分支上启用它。

2. 压缩后重新注入上下文。 这个更实用。

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Project rules: use Bun not npm. Tests must pass before commit.'"
          }
        ]
      }
    ]
  }
}

SessionStart 匹配 compact 意味着每次对话被压缩后自动注入项目约定的摘要。注意:写固定的规则用 CLAUDE.md 就够了。这个 Hook 适合注入动态信息,比如 git log --oneline -5 把最近的 commit 历史喂进去。

三个踩坑记录

Hook 脚本被 shell 配置污染。 这是最坑的一次。我的 ~/.zshrc 里有一行 echo "Shell ready"——非交互式 shell 也会执行。Hook 跑脚本时这行被打到 stdout 里,跟脚本的 JSON 输出混在一起,Claude Code 解析 JSON 失败。

解决方法:把 echo 包在交互式检测里。

if [[ $- == *i* ]]; then
  echo "Shell ready"
fi

PostToolUse 不能撤销操作。 工具已经执行完了,你只能记录或追加处理。要拦截在事前,用 PreToolUse

Hook 默认超时 10 分钟。 对大多数场景够用,但 UserPromptSubmit 只有 30 秒。如果你在这个 Hook 里跑慢操作,指定 "timeout": 60

实际效果

配完这几个 Hook 之后,几个收益:

  • 代码格式不再需要口头提醒,每次编辑完自动处理
  • 敏感文件自动保护,改不动
  • 长时间任务结束有通知,切窗口不需要来回检查
  • 压缩后项目约定不丢失

Hooks 和 prompt 的区别是:prompt 是跟 AI 商量,Hooks 是写进系统的规则。商量有时候管用,规则每次都管用。

下一步

Hooks 搞定了编译时和运行时的自动化。接下来想试试把 Claude Code 跟 OpenClaw 打通——Claude Code 写代码,OpenClaw 跑部署和监控。两个 Agent 协作,不只是一个。

系列文章和进度在 Vibe Coding 工作流