NeuralOS
GuideAdvanced

Claude Code 的 Hooks · 用 agent 生命周期事件自动化你的工作流

你已经会为你的 agent 写一份好合同:你在 AGENTS.md 里告诉它你希望它怎么工作。问题是 LLM 读了那份合同后,每一轮都重新决定——所以那条关键规则几乎总被遵守,而那个\"几乎\"恰恰就是咬你一口的那次。hooks 是下一级台阶:你把规则从模型的脑子里拿出来,塞进基础设施里,好让它 100% 的时候都发生,不依赖它的判断。我给你讲你挂钩的那些生命周期事件、JSON 的解剖、每个 hook 住在哪里以及为什么重要,并留给你三个真实、拿来即贴的 hook(自动格式化、阻止危险命令、桌面通知),取自 Anthropic 的官方文档。一个主 prompt,把安全装好来搭起你的第一个——因为一个 hook 用你的权限跑 shell。这是从\"写合同\"到\"让合同自己执行\"的那一跃。

Jul 19, 202614 min
这份内容是给谁的?
给你——已经在用 Claude Code 做 AI 开发、连着好几周都在重复同一句话的人:"记得格式化"、"别碰 .env"、"提交前先跑测试"。你自己会忘,AI 也会忘,然后某一天一个本不该进来的改动就混进去了。hooks 就是从"我请它记住"跃迁到"总是会发生,哪怕谁都没记住"的那一步。你不需要是程序员:你只需要理解一个模式,复制三段 JSON。

1. 那个瞬间 · 当你发现"求 AI 帮忙"根本不够

每个用 AI 的项目都会走到一个节点,你不再指望模型的一片好心。你在 AGENTS.md 里写了让它格式化代码、别碰敏感文件、跑测试。90% 的时候它都做到了。但剩下那 10% 恰恰就是咬你一口的那一次:它改了生产环境 .env 的那一次,它跳过格式化、把提交搞脏的那一次,它对着错误的文件夹执行 rm -rf 只因为"看起来像临时的"那一次。

hooks 的瞬间就是你想到:"要是这件事不取决于模型自己决定做对呢?要是它就是会发生、总是发生、不取决于它的判断呢?"。这正是 Claude Code 官方文档在 hooks 页面正中央摆出的承诺:它们给了对 agent 行为的 "确定性控制","保证某些动作总是发生,而不是取决于 LLM 是否选择执行它们"(忠实翻译自英文原文)。这句话就是整个资源的核心论点。

这样想象一下
一份合同(你的 AGENTS.md)就像贴在工厂墙上的规章:它说事情该怎么做,你相信每个工人都会读、都会遵守。而一个 hook 是传送带上的传感器,一旦某个零件出问题,它会从物理上把机器停下来。规章靠说服;传感器靠强制。hooks 就是那个传感器——它不客气地请求,它直接切断电流。

2. 痛点 · 它从哪来,为什么会发生

痛点不是一场末日,而是一种侵蚀。你让 AI 去做某件"总是"要做的事,可它有时不做,因为 LLM 不是确定性的:每一轮它都重新决定,而在一个塞满上下文的轮次里,你那句"记得格式化"的提醒可能被上千个别的 token 埋掉。不是 AI 不听话——而是你在向一个概率系统索要一致性。这就像求一个聪明但心不在焉的人永远别忘了关煤气阀:大多数晚上他都关了,而这恰恰是你放松警惕的原因。

你需要 hooks 的信号
你在每次会话里重复同一条指令("格式化"、"别碰 X"、"跑测试"),可它有时还是跳过
事后才发现 AI 改了一个它绝对不该碰的文件:.env、某个 package-lock.json.git/ 里的某样东西。
一次提交带着不一致的格式混了进来,把整个团队的 diff 搞脏了。
你有点不敢给 AI 大范围的权限,因为没有一张不依赖它自己的安全网
你的 AGENTS.md 里用散文写着关键规则,而"写下来"和"有保证"根本不是一回事。
最常见的误解
很多人以为只要在 AGENTS.md 里用大写字母加感叹号写规则,它就被"强制"了。并没有。无论语气多强烈,它依然是模型读到、然后决定听或不听的文本。要让某件事真正发生——100% 的时候、没有例外——唯一的办法是把它从 LLM 的脑子里拿出来,塞进基础设施里。那就是 hooks。

这里就是让这个资源进阶的角度:如果你已经读过关于为 agent 写一份好合同(AGENTS.md)的内容,那么 hooks 就是下一级台阶。你从"写合同"(声明式:告诉它你要什么)跨到"让合同自己执行"(确定性:机器替你落实它)。这是一部写在纸上的法律,和一个站在街角的警察之间的区别。

3. hook 是什么(不玩虚的,一句话)

一个 hook 是 Claude Code 在其生命周期某个具体节点自动执行的一条 shell 命令。你在一个 settings.json 文件里声明它一次;从那以后,每当到达生命周期的那个节点,你的命令就会运行。它不是由模型凭判断触发的:它由事件触发。魔法就在这里——它是确定的,不容商量。

底层的思路
Claude Code 在工作时会不断发出信号:"我刚收到一个 prompt""我正要用一个工具""我编辑完一个文件了""我要压缩上下文了""我回答完了"。一个 hook 就是挂到其中某个信号上,然后说:"当那件事发生时,运行这个"。挂钩(to hook)——名字和 🪝 表情就是这么来的。

4. 生命周期事件(你挂钩的那些节点)

官方文档列出了许多生命周期事件——几乎 agent 工作的每个时刻都有一个。别去背数量或整个列表:理解这个思路,记住你 95% 的时候真正会用到的那几个。以下就是它们,连同官方文档里原样出现的真实名称:

重要的那些事件(官方文档里的真实名称)
`SessionStart` — 当一个会话启动或恢复时。适合注入新鲜的上下文(比如你的约定、最近的提交)。它输出到 stdout 的内容会被加入 Claude 的上下文。
`UserPromptSubmit` — 就在你发送一个 prompt、Claude 还没处理它之前。你可以添加上下文,甚至阻止这个 prompt。
`PreToolUse` — 在一个工具执行之前这个能阻止动作。它是你的保镖:在这里你拦下一个 rm -rf,或对受保护文件的编辑。
`PostToolUse` — 在一个工具成功之后。在这里你格式化刚编辑的文件、跑一个 lint,随你想干什么。
`Notification` — 当 Claude 需要你时(请求权限或正在等待)。用来发一个桌面通知再合适不过。
`Stop` / `SubagentStop` — 当 Claude(或某个子 agent)回答完毕时。适合在把这一轮判定为结束前做一次最终检查。
`PreCompact`(以及带 compact matcher 的 SessionStart)— 围绕上下文压缩,好在记忆被压缩时不丢掉重要的东西。
`SessionEnd` — 当会话结束时。用来清理临时文件、收尾。
把一切说清楚的那个区分 · Pre vs. Post
`PreToolUse` 发生在之前,而且能阻止(工具还没执行,所以你可以否决它)。`PostToolUse` 发生在之后,而且无法撤销(工具已经跑了;文档直言不讳)。心里的规则:如果你想阻止某事,挂到 Pre。如果你想对已经发生的事做出反应(格式化、提醒、记录),挂到 Post

5. 一个 hook 怎么声明(JSON 的解剖)

每个 hook 都住在你 settings.json 里的一个 "hooks" 块中。结构永远是同一个套娃:事件名 → 一个 matcher(应用到什么上)→ 一个 hooks 列表,里面是 type: "command" 和要运行的 command。看一遍你就再也忘不掉:

json
{
  "hooks": {
    "PostToolUse": [                    // 1. 生命周期的 EVENTO(事件)
      {
        "matcher": "Edit|Write",         // 2. 应用到 QUÉ(哪些工具)
        "hooks": [
          {
            "type": "command",           // 3. 这是一条 shell 命令
            "command": "...tu comando..." // 4. 发生时 QUÉ(运行什么)
          }
        ]
      }
    ]
  }
}
matcher,说简单点
matcher 过滤 hook 应用到什么上。对于工具事件(PreToolUsePostToolUse)它按工具名过滤:"Edit|Write" 意思是"只在用 Edit 或 Write 时","Bash" 只针对 shell 命令。一个空的 matcher("")总是触发,对一切生效。竖线 | 分隔备选项(在较新版本的 Claude Code 里逗号也可以:"Edit, Write" 是等价的)。

6. 一个 hook 住在哪里(以及位置为什么重要)

你放 hook 的位置定义了它的作用范围。这很关键:一个项目级 hook 会跟你的团队共享(它被提交进仓库);一个全局的只属于你。官方表格总结了每样东西该放哪:

一个 hook 可以住的三个地方
`~/.claude/settings.json` — 全局,应用到你所有的项目。不共享,是你机器上的。这里放你的个人 hooks(比如桌面通知)。
`.claude/settings.json`(在仓库里)— 只针对那个项目,而且可以提交:你整个团队都继承这条规则。这里放项目规则(格式化、受保护文件)。
`.claude/settings.local.json` — 只针对那个项目,私有:Claude Code 在创建它时会加进 gitignore。用于你不想共享的本地设置。
位置的黄金法则
如果这条规则必须由在这个仓库里工作的每个人遵守(格式化、别碰敏感文件),它就进 .claude/settings.json 并被提交。这样合同就不再住在某一个人的脑子里,而是跟着代码一起走。这才是真正的力量:纪律成了仓库的一部分,而不是某人得记住的便利贴。

7. 三个复制粘贴、字字千金的 hook

理论够了。这里有三个真实的 hook,取自官方文档,拿来即贴。从今天最让你痛的那个开始。

a) 每次编辑后自动格式化(PostToolUse)

经典款。每当 Claude 编辑或写入一个文件,自动过一遍 Prettier。再也不会有格式不一致的提交了。放进你项目的 .claude/settings.json:

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}
底层怎么工作(免得它成了魔法)
当事件触发时,Claude Code 通过标准输入(stdin)把一个带数据的 JSON 传给你的命令:用了哪个工具、针对哪个文件等等。这里 jq -r '.tool_input.file_path' 从那个 JSON 里提取被编辑文件的路径,然后 xargs npx prettier --write 把它传给 Prettier。jq 是一个命令行 JSON 阅读器——在 macOS 上用 brew install jq 安装,在 Debian/Ubuntu 上用 apt-get install jq

b) 阻止像 rm -rf 这样的危险命令(PreToolUse)

这个能帮你摆脱恐惧。一个 PreToolUse hook,在每条 Bash 命令执行之前审查它,如果含有破坏性的东西就否决它。这里有一个文档讲得极清楚的技术细节很重要:通过以退出码 2 结束来阻止,而你写到 stderr 的内容会作为解释传给 Claude,让它自我纠正。先看脚本:

bash
#!/bin/bash
# .claude/hooks/block-rm-rf.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q "rm -rf"; then
  echo "Bloqueado: 'rm -rf' no está permitido. Borra rutas concretas, no recursivo-forzado." >&2
  exit 2   # exit 2 = bloquea la accion; stderr le llega a Claude como feedback
fi

exit 0     # exit 0 = sin objecion; sigue el flujo normal de permisos

让它可执行,并在你的 settings 里指向这个脚本来注册它:

bash
chmod +x .claude/hooks/block-rm-rf.sh
json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm-rf.sh"
          }
        ]
      }
    ]
  }
}
说实话 · 这个 grep 是个演示,不是密不透风的锁
一个 grep -q "rm -rf" 能抓住那个显而易见的情况,但它会漏掉 rm -fr、带奇怪空格的 rm -rf,或者 rm --recursive --force。它当一张看得见的网、当给 AI 的教育性反馈是有用的,但别把它当成盔甲。文档本身直言不讳:hooks 的过滤是尽力而为(best-effort)且"失败即放行"(如果它无法解析命令,就让它过),所以对于一条谁都不能绕过的禁令,用权限系统(permissions.deny),而不是一个 hook。正确的模式是:hook 用来反应和提醒;permission rules 用来做真正的锁。
真正救你的那个细节 · hook 连在 bypass 模式下也赢
这是最强、几乎没人知道的一点:根据官方文档,一个返回阻止决定的 PreToolUse 会否决该工具,即使在 `bypassPermissions` 下或带着 `--dangerously-skip-permissions`。也就是说:哪怕你(或 AI)已经把所有权限护栏都放下了,你的阻止 hook 依然屹立。这条规则是不对称的,而且是故意这么设计的:hooks 可以收紧策略,永远无法把它放松到超出权限规则允许的程度。一个阻止 hook 是一条连你自己都不会意外绕过的规则。

c) 当 AI 需要你时发桌面通知(Notification)

这样你就不用一直盯着终端了。当 Claude 完成或需要你的权限时,弹出一个桌面通知,你就可以去做别的事。放进你全局的 ~/.claude/settings.json(macOS 用 osascript 的示例):

json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Claude Code necesita tu atención\" with title \"Claude Code\"'"
          }
        ]
      }
    ]
  }
}
在 Linux 和 Windows 上
同一个 hook,只需换掉命令:在 Linux 上用 notify-send 'Claude Code' 'Claude Code necesita tu atención';在 Windows(PowerShell) 上用 System.Windows.Forms 的一个 MessageBox。JSON 的结构完全一样——只有 command 那一行变。指南本身记录的一个 macOS 小技巧:如果通知没出现,先跑一次 osascript -e 'display notification "test"',然后在"系统设置 → 通知"里给 Script Editor 授予权限。

8. 习惯 · 什么时候该想到 hook(什么时候不该)

hooks 不是万能的。心里的规则很简单:如果某件事必须总是发生、机械地、无需判断,那就是一个 hook。如果某件事需要逐案的判断,别用一个确定性的 hook 去硬来——让模型来决定(或用文档同样支持的 prompt/agent 类型 hook,不过那是另一个层级的东西)。

总是该想到 hook 的时刻
每当你发现自己在 AGENTS.md 里第三次写下"记得……"。如果你在重复它,就把它变成 hook
当有一个动作绝对不该发生时(碰 .env、递归删除、写入 .git/):那是一个阻止型 PreToolUse——由一条权限规则做硬锁托底。
当有一个动作在编辑后总该发生时(格式化、排序 import、跑一个 lint):那是一个 PostToolUse
当你想让某个关键上下文在每次压缩后被重新注入,好让 AI 不会在会话中途"忘记"你的约定时。
hooks 诚实的边界
一个命令型 hook 跑的是确定性的 shell:它适合机械规则(这条路径可以,这条不行),不适合带细微差别的判断("这个改动是不是个好主意?")。还有注意 PostToolUse:它无法撤销已经发生的事——它只反应。要阻止,永远用 PreToolUse。往一个 hook 里塞太多复杂逻辑会让它变脆;让它们保持简短、单一目的。
安全 · hooks 用你的权限跑 shell
官方文档直言不讳地警告过,而且必须重复:一个 hook 会用你的凭证、自动地执行任意 shell 命令。永远别贴一个你不理解的 hook,绝不从一个不可信的来源复制 hook,保存前审查每一条命令。一个恶意 hook 就是一条自己运行的恶意命令。像读任何你要在自己机器上运行的脚本那样读它们。

9. 使用协议 · 搭起你第一个有用的 hook

这里有个实用细节:文档本身建议你把 hook 描述给 Claude,让它替你写出来。但要写得好——而且安全——最好给它一个结构化的委托,而不是一句"给我弄个 hook"。这就是本资源的那个主 prompt:唯一一个、强大的、把安全刹车都装好的、来设计你第一个 hook 的 prompt。

主 prompt · 设计你第一个有用的 hook(带安全)文本
我想搭起我第一个 Claude Code hook,来自动化我工作流里的一条规则。我想要永远保证的那条规则是:[DESCRIBE LA REGLA,例如:"用 prettier 格式化你编辑的每个文件" / "阻止任何以递归方式删除文件的命令" / "禁止编辑 .env 或 .git/ 里的任何东西" / "完成时用通知提醒我"]。

在写任何东西之前,用清晰的语言(我不会编程)回答这些,帮我把它设计好:

1. 这条规则对应生命周期里正确的 EVENTO(事件)。如果规则是要 IMPEDIR(阻止)某事,它必须是 PreToolUse(发生在之前且能阻止)。如果是对已经做完的事做出 REACCIONAR(反应)(格式化、提醒、记录),它必须是 PostToolUse 或 Notification。告诉我你选哪个以及为什么。

2. 精确的 MATCHER(应用到哪些工具:Edit|Write、Bash,或空表示全部)以及为什么是这个而不是别的。让它尽可能地窄:一个过宽的 matcher 会在不该触发的地方触发 hook。

3. settings.json 该住的 LUGAR(位置):
   - 如果这条规则必须由这个仓库里整个团队遵守 → .claude/settings.json(会被提交)。
   - 如果只针对我的机器和我所有的项目 → ~/.claude/settings.json。
   - 如果是本地且私有的 → .claude/settings.local.json。
   告诉我哪个以及为什么。

4. 完整的 JSON 块,拿来即贴,hook 已经装好。如果它需要一个单独的脚本(带 PreToolUse 的阻止里很典型),也给我完整的 .sh 脚本,并提醒我用 chmod +x 让它可执行。

5. 如果它是一个阻止型 hook:用 exit 2 加一条清晰的 stderr 消息,向我解释为什么被阻止,好让你自己(Claude)收到理由并自我纠正。别让它静默。而且要 AVÍSAME(提醒我)这条规则是否还值得配一条权限规则(permissions.deny)当硬锁,因为一个 hook 里的 grep 是尽力而为、可以被绕过的。

6. 安全,必填:用一句话向我解释这个 hook 会跑 QUÉ(什么)shell 命令,并向我确认它不做任何破坏性的事、也不把数据发到任何地方。提醒我一个 hook 是用我的权限跑 shell 的,我只该保存我理解的命令。

7. 我怎么测试它:告诉我怎么验证 hook 已注册(Claude Code 里的 /hooks 命令),以及一种在不破坏任何真实东西的前提下测试它确实会触发的方法。

先只给我看 SOLO(仅)设计(事件、matcher、位置),然后是 JSON,如果需要再给脚本,最后是安全解释和测试。别现在就改我的 settings.json:我想在自己贴之前先审查这个块。

注意这个模式:先设计,再审查安全,然后由你来贴块。你绝不让一个 hook 在你还没理解它之前就出现在你的配置里。这跟保护任何强大自动化的两步纪律是一样的:机器提议,你批准。

10. 最省事的路径 · 什么通过聊天做,什么通过文件做

为了别把你绕晕,这就是你可以通过聊天委托给 AI 的,以及依然归你自己管的:

AI 替你做的(通过聊天)
如果你把规则描述给它,替你写出完整的 hook JSON 块(官方文档明确推荐这么做)。
替你生成阻止型 hook 的 .sh 脚本,带上它的 jq、它的 exit 2 和它给 stderr 的消息。
向你解释哪个事件和哪个 matcher 契合你的规则,以及它该住在哪个 settings.json 里。
提醒你安装步骤:脚本的 chmod +x,缺 jq 的话 brew install jq
由你决定并去做的(别委托出去)
把块贴进你的 settings.json——在读过并理解之后。安全网不能是你没审查过的代码。
选择位置(共享仓库 vs. 全局 vs. 本地):这决定谁继承这条规则。
确认 hook 的命令是安全的(它用你的权限跑 shell:如果你不理解,就不保存)。
在 Claude Code 里用 `/hooks` 验证它已注册,并测试它在不破坏任何真实东西的前提下会触发。
验证的窍门
在 Claude Code 里,输入 `/hooks` 打开 hook 浏览器:你会看到所有已配置的 hook,按事件分组,连同它们的 matcher 和命令。它是只读的(要编辑,你去动 JSON 或让 AI 来),但它是你确认 hook 存在且在该在的地方的凭据。如果它没出现,检查 JSON 是否有效(不能有悬挂的逗号或注释)、文件是否在正确的路径上;有时重启一下会话让文件监视器把它捡起来就够了。

总结 · 你的 hooks 清单

在把一个 hook 判定为搭好之前,确认
我选了正确的事件:PreToolUse 用于阻止,PostToolUse/Notification 用于反应。
matcher 尽可能地窄(不在不该触发的地方触发)。
它在正确的位置:共享仓库、你自己的全局,或本地私有。
如果它阻止,用 `exit 2` 加一条清晰的 stderr 消息让 AI 自我纠正——如果是硬禁令,我用一条权限规则给它托底。
理解它运行的命令(用我的权限跑 shell):我没保存任何我没审查过的东西。
我用 `/hooks` 验证过它,并测试了它在不破坏任何真实东西的前提下会触发。
我把规则从我 AGENTS.md 的散文里拿了出来,变成了总是会发生的东西。
在 NeuralOS 里 · 同一种纪律,平台级的规模
hooks 具象化了一个我们在 NeuralOS 里认真对待的想法:那一层里,关键规则不取决于任何人的判断,因为基础设施本身强制它们。这正是平台里 Supabase 的 advisors gate 在底层所做的:在封定任何数据库改动之前,一个自动检查自己运行,并且如果有东西违反了安全规则就阻止合并。没人需要"记得"去审查它——它总是发生,就像一个否决不该进来之物的 PreToolUse。这是一个 hook 的同一种哲学,只不过应用到了一整个团队而不是一个终端上:让质量不是某人记得的一个承诺,而是传送带上一个绕不过去的传感器。你把规则放一次;系统永远替你落实它。
C-A-R 协议 · 无 bug 地构建
hooks 是 C-A-R 那条确定性的手臂:协议要求纪律的地方,一个 hook 把它变成自动的。让质量不取决于记性的完美搭档。
Loop engineering · AI 独自朝目标工作
当你让 AI 在一个循环里独自迭代时,hooks 就是它的刹车和护栏:保证某些事在每一圈里(或不)发生,不取决于它的判断。
官方文档 · Automate actions with hooks (Anthropic)
本资源一切内容的来源:生命周期事件、复制粘贴的示例,以及安全方面的考量。在共享环境里部署 hooks 之前先读它。
#hooks#Claude Code#自动化#生命周期#进阶#确定性
Ready to build?

Start building in
under 3 minutes

Join 4,200+ builders. No credit card. Build your first app with AI in minutes.