用过 Claude Code 的人都知道一个问题:你告诉它"每次改完文件跑一下 Prettier",它有时候会照做,有时候会忘。你说"别动我的 .env 文件",它多数时候会听,但偶尔还是会手贱改一下。

这不是 Claude Code 的 bug,是大语言模型的本质——概率性输出。你用自然语言下的指令,它不保证每次都执行。

Hooks 就是来解决这个问题的。它是 Claude Code 的生命周期脚本机制,在特定时机自动触发 shell 命令,跟 Git Hooks 一个道理。不靠提示词,不靠祈祷,每次都跑。

这篇文章不讲概念,直接上 6 个我在用的配置,你复制粘贴就能跑。

hooks 的基本结构

所有 hook 配置写在 .claude/settings.json 里。一个项目一份,提交到 Git 后全团队共享。

{
  "hooks": {
    "PreToolUse": [
      {
        "type": "command",
        "matcher": "Bash",
        "command": "你的脚本或命令",
        "timeout": 30000
      }
    ]
  }
}

几个关键字段:

  • type:三种——command(跑 shell 命令)、prompt(注入文本到对话)、agent(启动子 Agent)。日常用 command 就够了
  • matcher:正则匹配工具名,区分大小写。Bash 匹配 shell 命令,Edit|Write 匹配文件编辑操作
  • timeout:毫秒,默认 60000,超时直接 kill

触发时机最常用的有四个:

事件 触发点 能干嘛
PreToolUse 工具执行前 拦截危险操作
PostToolUse 工具执行后 自动格式化、git add
Notification Claude 等待用户输入 桌面提醒
Stop Claude 完成回复 跑测试、质量检查

PreToolUse 的 hook 有个特殊能力:exit code 为 2 时直接阻断操作。这是做安全防护的核心。

配置1:自动格式化——改完文件就跑 Prettier

这是最高频的场景。Claude Code 改完代码后自动格式化,不用你每次提醒。

{
  "hooks": {
    "PostToolUse": [
      {
        "type": "command",
        "matcher": "Edit|Write",
        "command": "FILE=$(cat | jq -r '.tool_input.file_path // empty') && [ -n \"$FILE\" ] && npx prettier --write \"$FILE\" 2>/dev/null || true"
      }
    ]
  }
}

原理很简单:PostToolUse 触发时,hook 从 stdin 读取 JSON,用 jq 提取文件路径,然后跑 Prettier。末尾的 || true 是兜底——遇到 Prettier 不支持的文件类型(比如图片)不会报错中断。

前提:项目里装了 Prettier(npm install --save-dev prettier)。

我实测了两周,这个 hook 每天平均触发 40-60 次。以前要手动跑 npx prettier --write . 做全量格式化,现在完全不用了。

配置2:保护敏感文件——别碰我的 .env

这个我吃过亏才配的。有一次 Claude Code 把我的 .env 里的数据库密码给"优化"了——它觉得变量名不够清晰,自作主张改了 key 名。数据库连接直接挂了。

{
  "hooks": {
    "PreToolUse": [
      {
        "type": "command",
        "matcher": "Edit|Write",
        "command": "FILE=$(cat | jq -r '.tool_input.file_path // empty') && if echo \"$FILE\" | grep -qE '(\\.env|\\.lock|secrets\\.yaml|credentials|id_rsa|\\.pem)'; then echo \"BLOCKED: Cannot modify protected file: $FILE\" >&2; exit 2; fi"
      }
    ]
  }
}

exit code 2 触发阻断,stderr 里的消息会反馈给 Claude,它会知道为什么操作被拒绝了。

这个配置保护的文件类型:.env.lock(锁文件)、secrets.yamlcredentialsid_rsa.pem。你可以改 grep 的正则来匹配自己项目的敏感文件。

配置3:拦截危险命令——rm -rf / 永远别跑

理论上 Claude Code 不会生成这种命令,但谁知道呢。防一手总没错。

{
  "hooks": {
    "PreToolUse": [
      {
        "type": "command",
        "matcher": "Bash",
        "command": "CMD=$(cat | jq -r '.tool_input.command // empty') && if echo \"$CMD\" | grep -qEi '(rm\\s+-rf\\s+/|DROP\\s+TABLE|DROP\\s+DATABASE|mkfs\\.|:\\(\\)\\{|chmod\\s+-R\\s+777\\s+/|dd\\s+if=.*of=/dev/)'; then echo \"BLOCKED: Dangerous command detected\" >&2; exit 2; fi"
      }
    ]
  }
}

拦截列表: - rm -rf /——从根目录递归删除 - DROP TABLE / DROP DATABASE——SQL 删库 - mkfs.——格式化磁盘 - :(){ :|:& };:——fork bomb - chmod -R 777 /——根目录全开权限 - dd if=... of=/dev/——直接写磁盘

这些都是通过正则匹配的,大小写不敏感(-i 参数)。

配置4:命令日志——记录 Claude 跑了什么

调试的时候特别有用。每次 Claude 执行 shell 命令,自动记录到日志文件。

{
  "hooks": {
    "PostToolUse": [
      {
        "type": "command",
        "matcher": "Bash",
        "command": "cat | jq -r '\"[\" + (now | strftime(\"%Y-%m-%d %H:%M:%S\")) + \"] \" + .tool_input.command' >> .claude/command_log.txt"
      }
    ]
  }
}

日志长这样:

[2026-06-14 10:23:45] npm install axios
[2026-06-14 10:24:12] npx tsc --noEmit
[2026-06-14 10:25:01] git diff --cached

一个 session 下来翻一下日志,能清楚看到 Claude 做了什么。出了问题也好排查。

日志文件路径是 .claude/command_log.txt,建议加到 .gitignore 里。

配置5:改完文件自动 git add

搭配上面的命令日志一起用。Claude 改了哪些文件,全部自动 stage,你只需要 git diff --cached 审一眼,确认没问题直接 commit。

{
  "hooks": {
    "PostToolUse": [
      {
        "type": "command",
        "matcher": "Edit|Write",
        "command": "FILE=$(cat | jq -r '.tool_input.file_path // empty') && [ -n \"$FILE\" ] && [ -f \"$FILE\" ] && git add \"$FILE\" 2>/dev/null || true"
      }
    ]
  }
}

[ -f \"$FILE\" ] 这个检查很有必要——如果 Claude 删了一个文件,不加这个判断会报错。

我的工作流是这样的:Claude Code 改代码 → hook 自动 stage → 我跑 git diff --cached 看改动 → 确认后 git commit。比之前手动 git add . 再挑文件干净多了。

配置6:桌面通知——Claude 干完活叫你一声

跑长任务的时候你肯定会切到别的窗口。这个 hook 让 Claude 需要你输入时弹通知。

macOS:

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

Linux:

{
  "hooks": {
    "Notification": [
      {
        "type": "command",
        "command": "notify-send 'Claude Code' 'Claude needs your attention'"
      }
    ]
  }
}

这个 hook 建议放在用户级配置 ~/.claude/settings.json,而不是项目级。这样所有项目都能用。

完整配置:6个 hook 合并到一份 settings.json

把上面 6 个配置合并成一份完整的 .claude/settings.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "type": "command",
        "command": "FILE=$(cat | jq -r '.tool_input.file_path // empty') && if echo \"$FILE\" | grep -qE '(\\.env|\\.lock|secrets\\.yaml|credentials|id_rsa|\\.pem)'; then echo \"BLOCKED: Cannot modify protected file: $FILE\" >&2; exit 2; fi"
      },
      {
        "matcher": "Bash",
        "type": "command",
        "command": "CMD=$(cat | jq -r '.tool_input.command // empty') && if echo \"$CMD\" | grep -qEi '(rm\\s+-rf\\s+/|DROP\\s+TABLE|DROP\\s+DATABASE|mkfs\\.|:\\(\\)\\{|chmod\\s+-R\\s+777\\s+/)'; then echo \"BLOCKED: Dangerous command detected\" >&2; exit 2; fi"
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "type": "command",
        "command": "FILE=$(cat | jq -r '.tool_input.file_path // empty') && [ -n \"$FILE\" ] && npx prettier --write \"$FILE\" 2>/dev/null; [ -f \"$FILE\" ] && git add \"$FILE\" 2>/dev/null || true"
      },
      {
        "matcher": "Bash",
        "type": "command",
        "command": "cat | jq -r '\"[\" + (now | strftime(\"%Y-%m-%d %H:%M:%S\")) + \"] \" + .tool_input.command' >> .claude/command_log.txt"
      }
    ],
    "Notification": [
      {
        "type": "command",
        "command": "osascript -e 'display notification \"Claude needs your attention\" with title \"Claude Code\"'"
      }
    ]
  }
}

注意合并版里我把 PostToolUse 的 Edit|Write 合成了一条——先 Prettier 格式化,再 git add。减少 hook 数量,执行更快。

踩坑记录

用了两个月,碰到过几个坑:

1. matcher 区分大小写

写成 bash 不行,得写 Bash。写成 edit 也不行,得是 Edit。这个没有报错提示,hook 就是不触发,排查起来很恼火。

2. jq 没装会静默失败

hook 里大量用到 jq 解析 JSON。如果系统没装 jq,hook 会静默失败——不报错,不执行。macOS 用 brew install jq,Ubuntu 用 apt install jq

3. 超时默认 60 秒

跑测试之类的 hook 可能需要更长时间。超时后 hook 被 kill,操作会正常继续(不阻断),但你的测试结果就丢了。需要长时间运行的 hook 记得设 timeout

4. 路径问题

hook 里的相对路径是相对于项目根目录的,不是 .claude/ 目录。写脚本路径的时候注意这点。

调试方法

hook 不生效的时候,先用手动方式测试:

echo '{"tool_input":{"command":"git status"},"cwd":"/tmp"}' | bash .claude/hooks/your-script.sh
echo $?

这样可以确认脚本本身没问题,再排查 matcher 和事件类型是否写对。

另一个办法是用 Claude Code 内置命令 /hooks,它会列出当前加载的所有 hook 和它们的状态。


Hooks 做的事很简单:把你不放心交给概率的操作,变成确定性执行。配好之后基本不用管,默默在后台干活。

这 6 个配置覆盖了大多数日常场景。如果你有特殊需求——比如自动跑单测、自动更新 changelog、接 Slack 通知——原理都一样:选对事件,写好脚本,处理好 exit code。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐