← 返回全部文章
实战指南 · 2026年5月28日 · 8 分钟阅读

Claude Code Hooks 2026:4 个值得配置的 Hook,以及 3 个常见陷阱

打开 .claude/settings.json,你会发现能挂在上面的东西远比想象中多。到了 2026 年,Claude Code 的 Hook 系统已经从早期“保存时跑一下 prettier”的小技巧,长成了一层覆盖近三十个生命周期事件的机制:会话开始和结束、每次提示、每次工具调用前后、权限、压缩、子代理、worktree。能力越大,配错的方式也越多。本文只讲精简版:哪些 Hook 真正值得保留,以及走得更远的人最容易踩到的三个坑。

Hook 到底是什么

一个 Hook,就是 Claude Code 在某个生命周期事件触发时同步执行的一条命令——也可以是一次 HTTP 调用、一个 MCP 工具,甚至一个子代理。它配置在 settings 文件里,结构是:事件名 → matcher → 一组 handler。最典型的形态如下:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": ".claude/format.sh"
          }
        ]
      }
    ]
  }
}

matcher 匹配的是工具名:Edit|Write 表示两个都匹配,也可以写正则;空字符串或 * 则匹配全部。Hook 会通过 stdin 收到本次工具调用的 JSON(包括被编辑文件的路径),format.sh 再据此取出路径并格式化文件。事件触发、matcher 命中后,你的命令就会在你的机器上、用你的凭证运行。记住最后这半句——后面还会回来算这笔账。

值得配置的四个

在近三十个事件里,真正能在日常开发中站住脚的,其实只有这一小组:

  1. PostToolUse + Edit|Write → 自动 format。 经典做法,至今仍是性价比最高的 hook。Claude 写完文件,prettier / black / gofmt 立刻跑一遍,代码树保持整洁,不需要任何人额外操心。
  2. PreToolUse + Bash → 拦截危险命令。 用脚本检查命令里是否包含 rm -rfgit reset --hardgit push --force,命中就拒绝。这是一道真正的安全护栏——不过下一节会讲它怎么背叛你。
  3. SessionStart → 注入 context。 返回 additionalContext,就能把当前 git 分支、运行环境,或者一行代码库概览,放进 Claude 每次会话开头的上下文里。
  4. Stop / Notification → 完成后提醒我。 Claude 结束一轮回复后,让桌面弹个通知或终端响一声。只要你开始把长任务挂在后台跑,这个就值。

其余那些——worktree、子代理、压缩、elicitation——更多是给写插件或编排代理团队的人用的。普通开发场景里,少碰为妙。你每多加一个 hook,下面这三笔税就多交一份。

坑 1:阻塞税

hook 默认是同步执行的。一个 command 类型 hook 的 timeout 是 600 秒——在它返回(或超时)之前,Claude 会一直等着。而且只要 matcher 命中,它每次都会跑,没有去重或防抖。把一个会调用外部 linter 的 PostToolUse hook 挂到 matcher: "Edit|Write" 上,就等于给这个会话里的每一次文件编辑都叠加了那次 linter 的耗时。

这和臃肿的 CLAUDE.md 成为每个 session 的隐形 token 税是同一种形状——只不过这里交的是延迟税,而且按每次工具调用计费。解法是收窄触发范围:用 if 条件(例如 "if": "Bash(git *)" 只在 git 命令上跑),或者对不需要等待结果的 hook 加上 "async": true

坑 2:它 fail open,不是 fail closed

这是最容易伤人的一个坑。你写了一个 PreToolUse hook 来拦 rm -rf,于是觉得安全了。正常路径确实没问题:matcher 命中,脚本以 exit code 2 退出(或者 exit 0 并返回 JSON permissionDecision: "deny"),工具调用会被拦下。

但如果脚本自己崩了呢——jq 没装、路径写错、手误打错变量名?它会以 exit code 1(或 127)退出。而 Claude Code 会把任何“既不是 0 也不是 2”的退出码视为非阻断错误:它只在 transcript 里显示一行 “hook error”,然后照样执行那次工具调用

再读一遍:你的安全 hook 一旦自己挂了,门就开了。它 fail open,不是 fail closed。你指望用来拦住 rm -rf 的那道护栏,反而会在最糟糕的时刻把它放过去——比如脚本因为某个队友的环境和你不一样而崩掉时。于是有两条纪律:对安全关键的 hook,要么保证脚本不会崩,要么在每一条出错路径上都显式 exit 2;并且真的触发一次危险命令,确认它会被拦下,而不是假设它在工作。(这和只放 AGENTS.md、Claude Code 静默读到零条指令却从不报错是同一类失败:没报错,不等于在工作。

坑 3:它是共享文件里的可执行代码

.claude/settings.json 会提交进仓库,并在团队中共享。里面的 hook 可以运行任意 shell,而且使用的是运行者自己的凭证。把这两件事放在一起看:一个被合进 main 的 PostToolUse hook,意味着此后每个队友、每一次 Edit,都会在自己的机器上运行别人写进配置里的脚本——而 UI 在运行前并不会停下来问你一句。

所以 hook 不是普通配置,而是代码。PR 里要像 review 代码一样 review 它,而不是像扫一行设置那样带过。(这些 .claude 文件到底放在哪里、谁会读取它们,见 AI 编程代理到底往你磁盘写了什么。)在团队里,可以用 managed settings 的 allowManagedHooksOnly,把 hook 锁定在管理员审过的范围内——只要这个仓库不止一两个人共用,就值得这么做。

换成我们会怎么做

  • 个人: 配一两个就够了——format-on-write,也许再加一个完成提醒。其他先别碰。
  • 安全护栏: 想用 PreToolUse 拦截可以,但把它当代码来对待,并且真的去它:触发一条应该被拦下的命令,确认它确实 fail closed。
  • 团队: .claude/settings.json 里的每个 hook 都要有 owner、经过 PR review,并和 CLAUDE.md 一起定期通读。真正搞垮你的通常不是某一个 hook,而是 settings.jsonCLAUDE.md 两份文件各自漂移,却没人对任何一份负责。

如果你的 .claude 目录里已经堆了一批没人记得为什么存在的 hook,旁边还放着一个谁都不确定是否仍然准确的 CLAUDE.md,这正是 CLAUDE.md 审计要解开的结:哪些 hook 在帮你,哪些在悄悄给每次调用加税,哪些已经 fail open 好几个月了。$299 个人,$799 2–10 人团队。

相关阅读

相关阅读


文章独立产出 · 编辑政策

继续阅读 →