《Claude Code 工程化实战》第 17 讲 Hooks 事件驱动自动化

📅 2026/7/12 3:10:25
《Claude Code 工程化实战》第 17 讲 Hooks 事件驱动自动化
本讲摘要前 16 讲讲了 SubAgent Skills Commands 三种能力扩展机制、本讲进入事件驱动——Hooks在 Claude 执行工具前后插入自定义检查、做硬约束。核心是 4 类事件PreToolUse 工具执行前 / PostToolUse 工具执行后 / Stop 主代理停止 / SubAgentStop 子代理停止 两类应用安全三件套拦截 / 保护 / 审计质量三件套格式化 / Lint / 测试。Hooks 是 4 种扩展机制中最底层的——它不描述做什么、而是在工具被调用的瞬间做检查。本讲的三条主线第一、Hooks 是什么?4 类事件 触发时机——PreToolUse 工具执行前 exit 2 拒绝 / PostToolUse 工具执行后可修改输出 / Stop 主代理停止可强制继续 / SubAgentStop 子代理停止配置在 .claude/settings.json 的 hooks 字段。第二、两类典型应用——安全三件套拦截 / 保护 / 审计、纵深防御 质量三件套格式化 / Lint / 测试、代码质量门。第三、Hooks vs Skills vs SubAgent 边界——Hooks 拦截硬约束、瞬间拦截/ Skills 描述软规范、LLM 看 description)/ SubAgent 隔离独立上下文;3 者的工程定位Hooks 是门卫,Skills 是能力,SubAgent 是角色。学完本讲、你应该能用 4 类 Hook 事件做安全拦截 / 质量门 / 审计日志、能在 .claude/settings.json 配出完整的 Hook 流水线PreToolUse 拦危险 PostToolUse 自动格式化 Stop 检查未提交、能区分 Hooks / Skills / SubAgent / Commands 4 种扩展机制的工程定位。 详细内容Hooks 是什么?4 类事件 触发时机前 16 讲我们学了 SubAgent独立上下文角色/ Skills自动发现能力/ Commands用户主动命令3 种扩展机制。本讲进入第 4 种——Hooks、这是最底层的扩展机制、直接在工具被调用的瞬间做检查。Hooks 的核心思想Claude Code 在执行工具Bash / Read / Edit / Write / Grep / Glob的关键时间点、触发事件、你可以在 .claude/settings.json 里配置 Hook 脚本、在事件触发时执行自定义逻辑拦截 / 记录 / 修改输出。4 类 Hook 事件事件 1PreToolUse工具执行前——Claude 即将调起某个工具时触发、Hook 脚本可以检查工具参数 / 命令、决定是否放行。退出码 0 放行、退出码 2 拒绝输出到 stderr 的内容会作为拒绝原因返回给 Claude。典型用途拦截危险命令rm -rf / curl | sh。事件 2PostToolUse工具执行后——工具执行完成后触发、Hook 脚本可以读取工具输出 / 决定是否修改。退出码 0 通过、退出码 2 阻断但工具已经执行。典型用途自动格式化写完 Python 跑 ruff format)/ 审计日志记录所有 Bash 命令。事件 3Stop主代理停止——主代理判断任务完成、准备停止时触发、Hook 脚本可以检查是否真的完成未提交改动 / 测试未跑。退出码 0 允许停止、退出码 2 强制继续Claude 收到后继续执行。典型用途提醒未提交的改动。事件 4SubAgentStop子代理停止——子代理完成时被触发、类似 Stop 但针对子代理。典型用途子代理完成后自动汇总报告到主对话。4 类事件的出场顺序是 PreToolUse → 工具执行 → PostToolUse → 返回结果→ 重复 → 主代理判断完成→ Stop / SubAgentStop。配置位置.claude/settings.json 的 hooks 字段所有 Hook 都配置在 .claude/settings.json 里、格式// .claude/settings.json 的 hooks 字段格式 { hooks: { PreToolUse: [ { matcher: Bash, hooks: [ {type: command, command: bash .claude/hooks/deny-pipe-exec.sh} ] } ], PostToolUse: [...], Stop: [...], SubAgentStop: [...] } }matcher 字段指定哪个工具触发——比如Bash只对 Bash 工具触发、“Edit|Write对编辑类工具触发、”*对所有工具触发。事件触发时机退出码语义典型用途配置复杂度PreToolUse工具执行前0 放行、2 拒绝拦截危险命令 / 拦截改 lockfile低读 JSON grep exit)PostToolUse工具执行后0 通过、2 阻断已执行自动格式化 / 审计日志低读 JSON 跑命令 写日志Stop主代理停止0 允许、2 强制继续检查未提交 / 检查测试通过中需要查询 git 状态 / 测试结果SubAgentStop子代理停止0 允许、2 强制继续子代理完成后自动汇总报告中类似 Stop、针对子代理安全三件套拦截 保护 审计Hook 的第一类典型应用是安全三件套——拦截 / 保护 / 审计、3 件套叠加形成纵深防御。件套 1拦截PreToolUse 拦危险命令PreToolUse 触发、Hook 脚本检查命令、危险命令 exit 2 拒绝。典型拦截 5 类命令rm -rf/rm -fr删除文件/目录— 误用会丢失数据。curl | sh/curl | bash/wget | sh下载并执行— 远程代码执行风险。git push --force强制推送— 会覆盖远程历史。chmod 777放开所有权限— 安全风险。dd底层磁盘操作— 误用会覆盖磁盘。Hook 脚本用 grep 匹配这些模式、匹配上就 exit 2,stderr 输出❌ 拒绝[命令]被禁止、原因…。件套 2保护Stop 时检查未提交改动Stop 触发、Hook 脚本跑git status看是否有未提交的改动。如果有、exit 2 强制 Claude 继续、提醒用户还有未提交的改动、是否需要 commit?。保护机制防止Claude 完成任务后没提醒用户 commit、用户就关闭了会话、改动丢失。件套 3审计PostToolUse 记录所有 Bash 命令PostToolUse 触发、Hook 脚本把所有 Bash 命令记录到 .claude/audit/.log、包含时间戳 / 用户 / 命令 / 退出码 / 输出摘要。审计日志可以• 事后追溯今天跑了哪些命令。• 安全事件调查什么时候改了权限 / 什么时候删了文件。• 团队规范审计团队成员用 Claude 时跑了哪些高风险操作。3 件套叠加形成纵深防御• 拦截层PreToolUse)——危险命令根本进不来。• 保护层Stop)——重要操作不会静默完成。• 审计层PostToolUse)——所有操作有据可查。3 层叠加、即使某一层失效、其他层也兜底。这是防御性编程在 AI Agent 时代的应用。质量三件套格式化 Lint 测试Hook 的第二类典型应用是质量三件套——格式化 / Lint / 测试、把代码质量门从人审自动化成机器审。件套 1自动格式化PostToolUse 写完代码立即跑 ruff format)PostToolUse 触发matcher“Edit|Write”Hook 脚本检查刚写的文件是不是 Python、是就跑ruff format file自动格式化。Claude 写完代码、机器立即格式化、不用人手动跑。件套价值把团队代码风格从团队成员自觉变成机器强制、任何 PR 都不会出现格式问题。件套 2Lint 检查PreToolUse 检查 Edit 工具是否修改了不该改的文件PreToolUse 触发matcher“Edit|Write”Hook 脚本检查文件路径、如果是不该改的文件比如 lock 文件 / 配置文件就 exit 2 拒绝。典型防止 Claude 误改 package-lock.json / yarn.lock / requirements.txt这些文件应该用 npm install / pip install 改、不应该直接编辑。件套 3测试Stop 时检查所有测试是否通过Stop 触发、Hook 脚本跑pytest tests/ -q看是否全过。如果有失败、exit 2 强制 Claude 继续、提醒测试未通过、需要修复。件套价值把测试通过从PR review 的人工检查变成机器强制门、任何 PR 提交前都过测试。3 件套叠加形成质量门• 写代码时自动格式化避免风格问题。• 改文件时 Lint 检查避免改错文件。• 完成任务时测试验证避免破坏现有功能。质量门 安全门、组成完整的Claude Code 防御体系——这是工程化实战的核心。维度安全三件套质量三件套触发事件PreToolUse拦截 Stop保护 PostToolUse审计PostToolUse格式化 PreToolUse(Lint) Stop测试拦截 vs 改进拦截为主拒绝危险操作改进为主自动格式化 / 提醒静默 vs 可见静默用户看不到拦截过程可见用户看到格式化 / 测试输出实施成本低grep exit 2)中需要 ruff / pytest / git)用户感知低很少触发、触发时拒绝中频繁触发、但用户不反感Hooks 配置实战settings.json 3 个 Hook 脚本完整的 Hook 配置需要 2 步.claude/settings.json 配置事件 钩子命令.claude/hooks/ 目录放 3 个 Hook 脚本。step 1.claude/settings.json 完整配置// .claude/settings.json(完整 Hook 配置) { hooks: { PreToolUse: [ { matcher: Bash, hooks: [ {type: command, command: bash .claude/hooks/deny-pipe-exec.sh}, {type: command, command: bash .claude/hooks/deny-extra-dangerous.sh} ] }, { matcher: Edit|Write, hooks: [ {type: command, command: bash .claude/hooks/deny-edit-lockfile.sh} ] } ], PostToolUse: [ { matcher: Edit|Write, hooks: [ {type: command, command: bash .claude/hooks/auto-format.sh}, {type: command, command: bash .claude/hooks/audit-log.sh} ] } ], Stop: [ { matcher: *, hooks: [ {type: command, command: bash .claude/hooks/check-uncommitted.sh}, {type: command, command: bash .claude/hooks/check-tests.sh} ] } ] } }这个配置覆盖了 3 类事件PreToolUse / PostToolUse / Stop)、每个事件配 1-2 个 Hook 脚本、形成防御 质量 审计完整链路。step 2.claude/hooks/ 目录放 3 个核心 Hook 脚本3 个核心脚本• deny-pipe-exec.sh(PreToolUse 拦截 pipe 执行• auto-format.sh(PostToolUse 自动格式化• check-uncommitted.sh(Stop 检查未提交每个脚本都是一个独立的 Bash 脚本、可以从 stdin 读 JSON包含工具名 / 参数 / 命令、从 stdout 输出结果、退出码 0/2 表示通过/拒绝。3 个 Hook 实战案例案例 1危险命令拦截5 种PreToolUse Hook 拦截 5 类危险命令rm -rf / curl | sh / git push --force / chmod 777 / dd。实现用 grep -E 匹配命令、匹配上 exit 2 stderr 输出拒绝原因。完整实现见后面5 类危险命令拦截规则代码块。案例 2自动格式化PostToolUse Hook 自动跑 ruff format写完 Python 立即格式化。实现从 stdin 读 JSON包含 file_path)、如果是 .py 文件就跑ruff format file。自动格式化的优势Claude 写代码时不需要记住 PEP 8、机器自动保证格式一致。案例 3审计日志PostToolUse Hook 记录所有 Bash 命令到 .claude/audit/.log。实现从 stdin 读 JSON包含 command)、追加一行[timestamp] command exit到日志文件。 审计日志的价值事后追溯 / 安全事件调查 / 团队规范审计。Hooks vs Skills vs SubAgent 边界第 14 讲说过4 种扩展机制——Commands(用户主动)/ SubAgent(角色层)/ Skills(能力层)/ Hook(机制层)。本讲专门讲 Hook 的工程定位。3 种机制的工程定位•Hooks门卫——在工具被调用的瞬间做检查,硬约束,瞬间拦截。负责不能做的事和必须做的事。•Skills能力——LLM 推理该用就用,软规范,渐进披露。负责该做的事。•SubAgent角色——独立上下文,name 显式调。负责复杂任务的隔离执行。3 者的工作层次不同Hook 在工具层(瞬时检查),Skill 在能力层(LLM 推理),SubAgent 在角色层(独立上下文)。3 者的协同典型协同场景危险操作 Skill(如 rollback Skill)需要用户主动 双保险——Skill 加 disable-model-invocation: true(LLM 看不到) Hook 拦截危险命令(双保险)。3 者形成用户主动调 Skill Skill 调用时 Hook 兜底的两层防御。典型反协同不要用 Hook 替代 Skill。Hook 只能拦截/记录,不能做复杂任务——比如代码审查,Hook 只能检查是否调用了 reviewer,不能实际跑审查。实际审查用 Skill 或 SubAgent。3 者的边界判断这件事是瞬间检查吗?是 → Hook。这件事是LLM 推理该用就用吗?是 → Skill。这件事需要独立上下文吗?是 → SubAgent。这件事是用户主动调吗?是 → Command。️ 实战代码 第 17 讲配套3 个 Hook 脚本完整实现deny-pipe-exec.sh / auto-format.sh / check-uncommitted.sh)#!/usr/bin/env bash # .claude/hooks/deny-pipe-exec.sh # 拦截 curl | sh / wget | bash 等 pipe 执行 set -e # 从 stdin 读 JSON(Claude Code 注入的 hook 输入) INPUT$(cat) COMMAND$(echo $INPUT | jq -r .tool_input.command // ) # 拦截 pipe 执行 if echo $COMMAND | grep -qE curl\s.*\|\s*(sh|bash)|wget\s.*\|\s*(sh|bash); then echo ❌ 拒绝:pipe 执行被禁止(curl/wget | sh/bash 是远程代码执行风险) 2 exit 2 fi exit 0#!/usr/bin/env bash # .claude/hooks/auto-format.sh # 写完 Python 立即 ruff format set -e INPUT$(cat) FILE_PATH$(echo $INPUT | jq -r .tool_input.file_path // ) # 只对 .py 文件做格式化 if [[ $FILE_PATH *.py ]]; then if command -v ruff /dev/null; then ruff format $FILE_PATH 2/dev/null || true echo ✅ 已自动格式化: $FILE_PATH fi fi exit 0#!/usr/bin/env bash # .claude/hooks/check-uncommitted.sh # Stop 时检查是否有未提交改动 set -e # 在 git 仓库内才检查 if ! git rev-parse --is-inside-work-tree /dev/null 21; then exit 0 fi # 检查未提交改动 if ! git diff --quiet HEAD 2/dev/null || [ -n $(git status --porcelain) ]; then echo ⚠️ 还有未提交的改动,记得 git commit 2 echo 2 git status --short 2 exit 2 # exit 2 强制 Claude 继续 fi exit 0 第 17 讲配套5 类危险命令拦截规则rm/curl|sh/git push --force/chmod 777/dd)#!/usr/bin/env bash # .claude/hooks/deny-extra-dangerous.sh # 拦截 5 类危险命令 set -e INPUT$(cat) COMMAND$(echo $INPUT | jq -r .tool_input.command // ) # 规则 1:rm -rf / rm -fr(递归删除) if echo $COMMAND | grep -qE \brm\s(-[a-zA-Z]*[rf][a-zA-Z]*\s|-[a-zA-Z]*[r][a-zA-Z]*f|-[a-zA-Z]*[f][a-zA-Z]*r)\s; then echo ❌ 拒绝:rm -rf 被禁止(防止误删数据,改用 rm -i 交互式删除) 2 exit 2 fi # 规则 2:curl | sh / curl | bash / wget | sh / wget | bash if echo $COMMAND | grep -qE (curl|wget)\s.*\|\s*(sh|bash|sudo); then echo ❌ 拒绝:pipe 执行被禁止(curl | sh 是远程代码执行风险) 2 exit 2 fi # 规则 3:git push --force / git push -f(强制推送) if echo $COMMAND | grep -qE git\spush\s.*--force|git\spush\s.*-f\b; then echo ❌ 拒绝:git push --force 被禁止(会覆盖远程历史,改用 git push --force-with-lease) 2 exit 2 fi # 规则 4:chmod 777(放开所有权限) if echo $COMMAND | grep -qE chmod\s777|chmod\s-R\s777|chmod\s-R\s0777; then echo ❌ 拒绝:chmod 777 被禁止(放开所有权限有安全风险,改用 644 / 755) 2 exit 2 fi # 规则 5:dd(底层磁盘操作) if echo $COMMAND | grep -qE \bdd\s; then echo ❌ 拒绝:dd 命令被禁止(底层磁盘操作,误用会覆盖磁盘,改用更安全的工具) 2 exit 2 fi exit 0 第 17 讲配套审计日志的 Bash 记录实现.claude/audit/.log)#!/usr/bin/env bash # .claude/hooks/audit-log.sh # 记录所有 Bash 命令到 .claude/audit/date.log set -e INPUT$(cat) COMMAND$(echo $INPUT | jq -r .tool_input.command // ) EXIT_CODE$(echo $INPUT | jq -r .tool_result.exit_code // ?) TIMESTAMP$(date %Y-%m-%d %H:%M:%S) # 创建 audit 目录 mkdir -p .claude/audit # 追加日志(每天一个文件) LOG_FILE.claude/audit/$(date %Y-%m-%d).log echo [$TIMESTAMP] exit$EXIT_CODE command$COMMAND $LOG_FILE exit 0# 审计日志示例(.claude/audit/2026-06-02.log) [2026-06-02 10:15:23] exit0 commandgit status [2026-06-02 10:16:45] exit0 commandgit diff --stat [2026-06-02 10:18:12] exit0 commandpytest tests/ -q [2026-06-02 10:25:33] exit0 commandruff format src/api.py [2026-06-02 10:30:18] exit2 commandrm -rf node_modules/ [2026-06-02 10:30:18] ❌ 拒绝:rm -rf 被禁止(...) # 审计日志的 3 个用途: # 1. 事后追溯:今天跑了哪些命令 # 2. 安全事件调查:什么时候改了权限 / 什么时候删了文件 # 3. 团队规范审计:团队成员用 Claude 时跑了哪些高风险操作⚠️ 常见坑⚠️ Hook 脚本写得太慢超过 1 秒、影响 Claude 响应速度Hook 性能陷阱PostToolUse Hook 每次 Claude 调 Edit/Write 都会触发、如果 Hook 跑得慢比如自动跑 pylint 而不只是 ruff format、要 5-10 秒,Claude 整体响应速度会被拖慢。修正Hook 脚本要轻——用 grep 匹配比用 python -c 跑复杂逻辑快缓存常用结果比如 ruff 检查过的文件不要重复检查长时间操作跑完整测试套件改用 Stop 触发而不是 PreToolUse。判断标准HW 脚本 1 秒 太慢、优化。⚠️ Hook exit 2 但没给清晰错误信息用户不知道为啥被拒“拒绝不解释的体验陷阱Hook exit 2 但 stderr 只输出❌ 拒绝没说原因、Claude 收到拒绝信号后不知道怎么办、用户也不知道为啥被拒。修正exit 2 时、stderr 必须输出清晰的拒绝原因——“❌ 拒绝rm -rf 被禁止、原因防止误删数据、改用 rm -i 交互式删除”。这样 Claude 收到后能告诉用户我被拒绝的原因、你可以用 rm -i 替代”、形成完整反馈链。判断标准Hook 拒绝时 stderr 输出的信息、用户能直接看懂原因?能、正确不能、补全。⚠️ Hook 拦截了合法操作比如 “rm /tmp/test” 是合法清理、被误拦误拦的过度防御Hook 规则写得太宽——所有 rm 命令都拦、结果rm /tmp/test.txt合法的临时文件清理也被拦。修正HW 规则要精准——只拦真正危险的模式rm -rf / rm -fr 关键目录、不拦普通 rm。规则写好后、在测试环境跑一组应该被拦的命令和应该被放过的命令、确保两个集合都正确。判断标准HW 误拦率 5%?调规则。⚠️ Hook 只配置没测试只在生产配置、没在测试环境跑过配了但没测的风险配置 Hook 后没在测试环境验证、直接上生产、结果某天发现HW 没生效可能是路径写错 / 权限问题 / jq 没装、危险命令没被拦下来。修正配置后立即在测试环境跑5 个应该被拦的命令和5 个应该被放过的命令、确认两个集合都正确。再用 Stop HW 的未提交检查故意制造未提交改动、确认 HW 能强制 Claude 继续。判断标准HW 配置后、你在测试环境跑过 10 个测试用例吗?没有、补测。 一句话备忘Hooks 是 4 种扩展机制中最底层的——在工具被调用的瞬间做硬约束PreToolUse 拦截 / PostToolUse 记录 / Stop 强制继续、形成安全三件套拦截 保护 审计 质量三件套格式化 Lint 测试的纵深防御。