Claude Code 进阶/Lifecycle Hooks:在 Agent 关键节点自动执行
Claude Code 进阶

Lifecycle Hooks:在 Agent 关键节点自动执行

理解 Claude Code 的生命周期 hook(PreToolUse / PostToolUse / Stop / Notification 等),何时该用、典型用例与避坑

难度 314 分钟pattern更新于 2026-04-22

内容摘要

Claude Code 的 **Lifecycle Hooks(生命周期钩子)**,可以理解成在 Agent 跑的过程中预埋的若干个"开关位"。每当 Agent 走到某个关键节点——比如"准备调一个工具"、"工具刚跑完"、"用户输入还没进 LLM"、"会话即将结束"——Claude Code 这个壳就会去检查 `settings.json`,看你有没有为这个节点注册脚本。注册了,它就把当前节点的全部上下文(工具名、参数、cwd、session_id 等)按 JSON 喂到脚本的标准输入;脚本怎么处理、要不要拦截,由你写的逻辑决定。

Lifecycle Hooks:在 Agent 关键节点自动执行

基础概念

Claude Code 的 Lifecycle Hooks(生命周期钩子),可以理解成在 Agent 跑的过程中预埋的若干个"开关位"。每当 Agent 走到某个关键节点——比如"准备调一个工具"、"工具刚跑完"、"用户输入还没进 LLM"、"会话即将结束"——Claude Code 这个壳就会去检查 settings.json,看你有没有为这个节点注册脚本。注册了,它就把当前节点的全部上下文(工具名、参数、cwd、session_id 等)按 JSON 喂到脚本的标准输入;脚本怎么处理、要不要拦截,由你写的逻辑决定。

为什么要单独做"hooks",而不是让 Claude 自己在 prompt 里"记得"在某些时刻去做某事?两个原因:

  1. 确定性。Claude 是概率模型,让它"记住每次写完 Python 文件都跑一次 ruff" —— 多跑几轮就会忘掉。Hook 是 CLI 这个进程外的事件钩子,不进 LLM 推理,100% 必跑。
  2. 零 token 成本。Hook 跑在 LLM 之外,本身不消耗模型 token。把"格式化、跑 lint、记审计日志、拦危险命令"这些机械动作全交给 hook,模型上下文就能省下来做真正需要思考的事。

一句话说人话:hook = "Claude 不参与的、由 CLI 这一侧自动触发的脚本"。它不是给 Claude 用的工具,而是 Claude Code 这个外壳给你提供的"自动化插槽"。

核心要素

要素作用
事件(event)一个生命周期触发点,比如 PreToolUsePostToolUseStopUserPromptSubmitSessionStart
匹配器(matcher)缩小触发范围,例如 "Bash" 只在调 Bash 工具时触发,"Write|Edit" 同时匹配两种文件写入
处理器(hook handler)真正被调起的脚本(type: "command")或 HTTP 端点(type: "http"
stdin(输入)Claude Code 通过标准输入传入完整事件 JSON:tool_nametool_inputcwdsession_id
退出码 / stdoutexit 0 + 可选 JSON = 正常;exit 2 = 阻断(仅对部分事件生效);其它非零 = 报警但不阻断
作用域~/.claude/settings.json(用户全局)、.claude/settings.json(项目共享)、.claude/settings.local.json(项目私有,不入库)

典型 hook 触发时序

下图画出一次"用户提问 → Claude 决定调 Bash → 工具执行 → Claude 输出 → 结束"完整 turn 中各 hook 的触发位置:

正在渲染 Mermaid 图表…

注意几个细节:PreToolUse 在工具真正跑之前,所以它是唯一能"拦下危险命令"的位置;PostToolUse 在工具已经成功执行之后,它能给 Claude 反馈但已经无法撤销操作——这是新手最常踩的坑(详见"常见误区")。

基础用法

写一个 hook 一共三步:① 在 settings.json 里登记事件 + 匹配器;② 写脚本读 stdin 里的事件 JSON;③ 用 exit code 或 stdout JSON 控制后续行为。

第一步:最小可运行配置

在项目根目录建 .claude/settings.json

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

这段配置的含义:每当 Claude 调用 WriteEdit 工具改完文件后,自动跑 .claude/hooks/auto-format.sh$CLAUDE_PROJECT_DIR 是 Claude Code 启动时注入的环境变量,指向项目根,路径鲁棒性比写死好。

第二步:脚本读 stdin、按需做事

新建 .claude/hooks/auto-format.sh,给它执行权限(chmod +x):

#!/bin/bash
# PostToolUse: 给刚刚被 Write/Edit 的文件按扩展名自动格式化

# Claude Code 通过 stdin 传入完整事件 JSON,这里用 jq 取出文件路径
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path')

case "$FILE_PATH" in
  *.py)   ruff format "$FILE_PATH" ;;
  *.ts|*.tsx|*.js|*.jsx) npx prettier --write "$FILE_PATH" ;;
  *.go)   gofmt -w "$FILE_PATH" ;;
  *)      exit 0 ;;  # 其他扩展名直接跳过
esac

# 把 lint 错误(如果有)回灌给 Claude
if ! ruff check "$FILE_PATH" 2>/dev/null; then
  echo "ruff 发现风格问题,请修复" >&2
fi

exit 0

预期效果:你让 Claude 改一个 app.py,Edit 工具刚执行完,Claude 还没开始下一句话,hook 已经跑完 ruff format,文件就已经是规范风格了;如果 lint 有问题,stderr 会被自动注入 Claude 的下文,让模型在下一步回复时主动修复。

第三步:拦截危险命令(阻断式 hook)

PreToolUse 是少数几个能"真的阻止 Claude 做某事"的事件之一。下面这个脚本会拦截一切 rm -rf 命令:

#!/bin/bash
# .claude/hooks/block-dangerous-bash.sh
# 注册到 PreToolUse + matcher: "Bash"

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')

# 黑名单:rm -rf、sudo、curl | sh 等
if echo "$COMMAND" | grep -qE '(rm\s+-rf|sudo|curl\s+[^|]+\|\s*sh)'; then
  # 用 hookSpecificOutput 给 PreToolUse 显式 deny
  jq -n --arg cmd "$COMMAND" '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: ("拦截高危命令:" + $cmd + "。请改用更安全的方案。")
    }
  }'
  exit 0
fi

exit 0

对应 settings.json 片段:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-dangerous-bash.sh" }
        ]
      }
    ]
  }
}

效果:模型若生成了 rm -rf dist/,命令根本不会跑到 shell,原因会回灌给模型,让它换一种方式(比如逐文件删)。这比依赖"我让 Claude 别用 rm -rf"的提示词靠谱得多。

速查:PreToolUse 控制工具执行的标准做法是 hookSpecificOutput.permissionDecision,可选值 "allow" | "deny" | "ask" | "defer",多个 hook 同时触发时优先级 deny > defer > ask > allow

Claude Code 当前支持的事件清单(节选)

事件触发时机能否阻断典型用途
SessionStart启动 / resume / 清屏 / 压缩后注入项目背景、最近 issue、环境变量
UserPromptSubmit用户按下回车后、prompt 进 LLM 前敏感词拦截、自动补充上下文
PreToolUse模型生成完工具参数、工具执行前拦截危险命令、改写参数、按规则放行
PostToolUse工具成功返回后否(无法撤销)自动格式化、跑 lint、写审计日志
PostToolUseFailure工具执行失败后补充错误诊断上下文
StopClaude 结束本轮回复时✅(可让它继续说)强制跑测试再收尾、待办未完成时阻止退出
SubagentStopsubagent 任务结束验收 subagent 产出
PreCompact / PostCompact上下文压缩前 / 后PreCompact 可阻断压缩前转储重要状态、压缩后注入摘要
NotificationClaude Code 弹通知(如等权限)推到桌面、机器人、企业微信
SessionEnd会话结束上传日志、写 token 计费、清理临时文件

同类工具对比

CLI Agent 周边有不少"自动化"机制,初学者常分不清。下面这张表把 hooks 和最容易混的几个邻居放一起对比:

维度Lifecycle HooksSkillsMCP 工具Cursor Rules / CLAUDE.md
触发方CLI 这一侧自动触发(事件驱动)Claude 自己根据情境显式调用Claude 自己显式调用(Tool Use)在每次推理时作为提示注入
是否进入 LLM 推理❌ 不进✅ Skill 描述 + 内容会进上下文✅ Tool 描述会进上下文✅ 全文注入
是否消耗 token
能否阻断模型行为✅(PreToolUse / Stop 等可硬拦)❌ 只能引导❌ 只是提供工具❌ 提示性
谁决定执行时机CLI(确定性)模型(概率性)模型(概率性)模型(概率性)
适合做什么强制 lint、安全拦截、审计、通知复杂工作流的可重用"剧本"接外部数据源、调远程服务写编码规范、风格约定
不适合做什么"让模型在这里做点决策"(hook 里没 LLM)强约束(模型可能忽略)同上任何需要确定性的动作

核心区别一句话:hooks 是 CLI 外壳的事件回调,确定性、零 token、能硬拦;Skills/MCP/Rules 是 喂给模型的上下文或工具,灵活但不可靠。要"必须执行"用 hooks,要"灵活执行"用 Skills/MCP,要"风格约定"用 CLAUDE.md。互相不能替代。

互链:MCP 和 hooks 的关系详见 /mcp/cards/what-is-mcp;Skills 和 hooks 怎么搭配见 /skills/cards 下相关卡。

常见误区

误区准确理解
以为 hook 能阻止 Claude "想"做某事hook 拦的是工具调用,不是模型推理。模型仍然会想,hook 只是不让对应动作发生在你机器上。想限制模型思考,靠的是 prompt 和权限规则,不是 hook
以为可以在 hook 里调 Claude 模型hook 跑在 Claude Code 进程之外、不在 LLM 推理上下文。脚本里没有"反过来问 Claude"的官方通道;要让模型介入只能写 decision: "block" 把控制权交回去、并附 reason 让模型在下一步处理
以为 PostToolUse 能撤销文件修改PostToolUse 触发时工具已经执行成功。文件已经被写、命令已经跑完。你最多能给模型"反馈"让它下一步去 revert,但 hook 本身不会回滚。要真拦改动只能用 PreToolUse
以为 exit 2 在所有事件都能阻断只有部分事件能被 exit 2 阻断(PreToolUse、UserPromptSubmit、Stop、PreCompact、SubagentStop、PermissionRequest 等)。在 PostToolUse / SessionStart / Notification 上 exit 2 只是把 stderr 当反馈喂回去,不会撤销已经发生的事
以为多个 hook 顺序执行同一事件命中的多个 hook 是并行执行的,相同的 command 字符串还会被去重。如果你希望 A 跑完再跑 B,要么写在同一个脚本里串起来,要么靠各自退出码触发后续逻辑
在 Stop hook 里无脑 exit 2 想"让 Claude 一直说"Stop hook exit 2 会把 Claude 拉回继续生成,但可能死循环——上次 stop 的原因若没解决,它会一直 stop、一直被拉回。务必加终止条件(达到次数、某文件存在等)
把秘钥硬编码进 hook 脚本hook 是 plain shell,提交进 .claude/settings.json 的内容会被同事看到。秘钥放环境变量,HTTP hook 用 allowedEnvVars 白名单注入 header,不要写在脚本里

优劣势分析

优势劣势
确定性:到点必跑,不依赖模型记性,特别适合"强制规范"场景(lint、format、审计)调试链路长:同一事件可能命中多条 hook、还可能并行跑,出问题时要翻 hook 日志、stdin JSON、stderr 三处定位
零 token 成本:跑在 LLM 之外,不占上下文窗口,长会话特别划算作用域有限:只能在 Claude Code 预定义的 12+ 个事件里挂,遇到没钩子的节点(比如"模型 thinking 中途")插不进去
能硬拦危险动作:PreToolUse + permissionDecision: deny 是目前防"AI 一时兴起 rm -rf"最可靠的护栏PreToolUse 是双刃剑:拦得太宽 Claude 寸步难行,拦得太松形同虚设。需要持续根据 false positive / negative 调整规则
跨语言、跨工具:脚本是 shell/python/任意可执行文件,hook 也支持 HTTP,不绑定具体语言生态配置散乱风险:用户级、项目级、local 级三层 settings 合并,加上插件 hooks,团队协作时容易出现"我机器拦了你机器没拦"
可与 Skills / MCP 互补:把"必须发生的副作用"放 hook,把"灵活的工具调用"放 MCP/Skills,分工清晰跨平台兼容性:默认 shell 是 bash/powershell,Windows 团队和 macOS/Linux 团队混跑时,要么写两套脚本要么统一走 Node/Python

思考题

初级:为什么不能在 hook 里"反过来调 Claude 模型问一个问题"?

参考答案:

Claude Code 把 hook 设计成进程外、单向的事件回调:CLI 把上下文 JSON 通过 stdin 推给你的脚本,等你拿 exit code 和 stdout 回报结论,整个交互就结束了。这个设计是刻意的,原因有三:

  1. 避免循环:如果 hook 能调 Claude,Claude 调工具又触发 hook,hook 又调 Claude……极容易陷入指数级 token 爆炸和无限递归。
  2. 保持确定性:hook 的核心价值就是"必跑、可预测"。一旦它能调 LLM,行为就重新变成概率性,用 hook 的初衷被瓦解。
  3. token 责任归属:模型调用要计费、要进上下文。hook 是 CLI 的本地副作用,让它直接花用户的模型钱、占模型上下文,权责模糊。

要让模型介入,正确做法是 hook 写 decision: "block" + reason: "...",把控制权交回 Claude Code,由它在下一轮推理时把 reason 注入上下文,让模型自己处理。

中级:项目里要做"AI 改了 Python 文件就强制跑 ruff,跑不过就让 Claude 自己修复"。该用 PreToolUse 还是 PostToolUse?为什么?

参考答案:

应该用 PostToolUse + matcher 为 Write|Edit。原因:

  • 你的目标是"改完之后跑 lint",前提是文件已经被改。PreToolUse 在文件还没写时触发,那时 ruff 检查的还是旧文件,没意义。
  • 让 Claude 自己修复 lint 错误,需要把 ruff 的报错"传"回模型。PostToolUse 脚本通过 decision: "block" + reason 或者直接 stderr + exit 2(在 PostToolUse 上 exit 2 会把 stderr 注入 Claude 的下文),模型下一步就会看到"ruff 报了 E501,请修复"。
  • 如果你希望"文件甚至别被写出来"——比如想拦截写入危险路径——那才用 PreToolUse 拦在写之前。

完整脚本骨架:

#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path')
[[ "$FILE" == *.py ]] || exit 0

if ! OUTPUT=$(ruff check "$FILE" 2>&1); then
  echo "ruff 报错,请修复后重试:" >&2
  echo "$OUTPUT" >&2
  exit 2   # PostToolUse 上 exit 2 = 把 stderr 注入下一轮上下文
fi
进阶:团队 5 个人用 Claude Code,希望"所有人都自动跑 lint,但敏感命令拦截规则各自定制",settings 文件应该怎么分?

参考答案:

利用 Claude Code 的三层 settings 合并机制:

文件入库放什么
.claude/settings.json✅ git 跟踪团队统一:PostToolUse 跑 lint / format、SessionStart 注入项目上下文
.claude/settings.local.json.gitignore个人私有:自己机器上的 rm 拦截、个人通知(推到自己的 Telegram 等)
~/.claude/settings.jsonn/a跨项目通用:全局生效的拦截、自己的审计日志路径

要点:

  1. 团队规则放项目级 + 入库,确保新成员 clone 即生效。
  2. 个人规则放 local 或用户级,避免污染他人。
  3. CI 环境可以在容器里只挂 .claude/settings.json,不带 local,行为更可预测。
  4. 如果有"组织强制"的规则(比如生产分支禁止 bypassPermissions),用 managed-settings.json 配合 allowManagedHooksOnly: true,普通 settings 无法覆盖。
  5. hooks 间的去重靠"command 字符串完全一致"——如果团队级和个人级写了一模一样的脚本路径,只会跑一次,不会重复执行。

参考资料

  1. Claude Code Hooks 官方参考:https://code.claude.com/docs/en/hooks(查询日期 2026-04-22)
  2. Claude Code Settings 官方文档:https://code.claude.com/docs/en/settings(查询日期 2026-04-22)
  3. Claude Code GitHub 仓库 - Issues 关于 hook 行为的讨论:https://github.com/anthropics/claude-code/issues/19009(PostToolUse exit 2 行为)
  4. Claude Code GitHub Issue #24327:https://github.com/anthropics/claude-code/issues/24327(PreToolUse exit 2 边界场景)
  5. Steve Kinney - Claude Code Hook Control Flow:https://stevekinney.com/courses/ai-development/claude-code-hook-control-flow
  6. Claude Directory Blog - Claude Code Hooks Guide 2026:https://www.claudedirectory.org/blog/claude-code-hooks-guide
  7. Pixelmojo - Claude Code Hooks: All 12 Events with Examples (2026):https://www.pixelmojo.io/blogs/claude-code-hooks-production-quality-ci-cd-patterns

延伸阅读

优先展示同分类且标签更接近的内容,方便继续串联学习。

Claude Code 进阶难度 314 分钟模式
01

用 CLI Agent 跑 TDD 工作流

用 CLI Agent 跑"红 - 绿 - 重构"循环:先写测试 → 让 Agent 写实现 → 自动跑测试 → 验证通过 → 重构的完整工作流

TDD测试驱动工作流Claude Code质量
更新于 2026-04-22tdd-with-cli-agent
Claude Code 进阶难度 219 分钟实践
02

在 CLI Agent 里挂 MCP server

在终端 Agent 里挂 MCP server 的两种方式 —— claude mcp add 命令 vs settings.json 配置文件,含 stdio 与远程 server 的踩坑要点

MCPClaude Codeclaude mcp add配置集成
更新于 2026-04-22configure-mcp-in-cli
Claude Code 进阶难度 217 分钟工具
03

Claude Code 入门

Anthropic 官方终端 Agent,原生支持工具调用、MCP、Skills 与 Hooks,2026 年生态最完整的 CLI Agent

Claude CodeAnthropic终端 AgentCLI入门
更新于 2026-04-22claude-code-overview