Claude Code 完整指南(四):Hooks(自动化事件触发)
文章目录
1. 引言

在前面的博客,博主已经讲解了 Claude Code 相关的概念,有兴趣的同学可以参考下:
- 《Claude Code 完整指南(一):安装、CLI 实战、IDE 集成一次讲透》
- 《Claude Code 完整指南(二):终端命令全解析(收藏级)》
- 《Claude Code 完整指南(三):命令背后的数据流动》
如果你已经在用 Claude Code 写代码,你一定遇到过这些“重复但必要”的动作:
- 写完文件要跑格式化、lint、单测
- 运行危险 Bash 之前想要一层保险(尤其是
rm -rf、git push --force) - 想在会话开始时自动检查环境(Node/Python 版本、依赖是否安装)
- 想在 Claude “准备停下”时强制做一次自检:测试过了吗?有未提交的改动吗?
Hooks 的价值就在于:把这些动作从“ 你记得做” 变成 “系统自动做” ,并且可以做到“该阻断就阻断、该后台跑就后台跑、该提醒就提醒”。
2. Hooks 是什么?
Hooks 是 Claude Code 的事件驱动自动化系统:在特定事件触发时,自动执行一组 Hook。
它主要有两种形态:
- 命令型 Hooks(command):运行 Shell 命令/脚本,适合自动化与校验(格式化、检查、通知、跑测试……)。
- 提示型 Hooks(prompt):在
Stop/SubagentStop事件里,让 Claude 再做一次“停下前的质量检查”,根据结果决定继续还是停止。
你可以把它理解为:
- SessionStart/End:会话生命周期钩子
- PreToolUse/PostToolUse:工具执行前/后钩子(Write/Edit/Bash/Read…)
- Stop/SubagentStop:模型准备结束前的“收尾门禁”
- Notification:系统通知的自定义处理
3. Hooks 配置(用户级 vs 项目级)
Claude Code 支持分层配置,Hooks 通常按是否团队共享来决定目录。
项目级(推荐团队共享):
- 文件:
{project}/.claude/settings.json - 适合:团队统一的格式化、测试、危险命令拦截、质量门禁等
- 配套建议:把脚本也放进仓库,例如
{project}/.claude/hooks/*.sh,让团队开箱即用
项目级本地(仅自己用,不提交):
- 文件:
{project}/.claude/settings.local.json - 适合:个人路径相关脚本、个人通知方式、私有工具等
- 配套建议:加入
.gitignore
用户级(跨项目生效):
- 文件:
~/.claude/settings.json - 适合:你个人的通用 Hooks(比如任何项目都启用的通知/危险命令拦截)
小贴士:团队规范建议 “项目级为主、用户级为辅”,项目级让仓库可复制;用户级放个人偏好。
4. 八种事件类型汇总
| 事件 | 触发时机 | Hook 类型 | 常见用途 |
|---|---|---|---|
| SessionStart | 会话开始 | 命令型 | 初始化环境、检查依赖、打印提示 |
| SessionEnd | 会话结束 | 命令型 | 清理临时文件、记录统计 |
| UserPromptSubmit | 用户提交输入前 | 命令型 | 输入验证、关键词拦截、注入上下文 |
| PreToolUse | 工具执行前 | 命令型 | 危险命令拦截、参数校验、审计 |
| PostToolUse | 工具执行后 | 命令型 | 自动格式化、自动测试、生成变更摘要 |
| Stop | Claude 决定停止前 | 提示型 | 质量门禁(lint/test/git status) |
| SubagentStop | 子代理停止前 | 提示型 | 子任务验收(比如“测试是否补齐”) |
| Notification | Claude 发送通知时 | 命令型 | 转发通知(系统通知/Slack/飞书等) |
5. 命令型 Hooks
写脚本就能自动化
5.1 基础语法(最常用)
{"hooks":{"PostToolUse":[{"matcher":"Write|Edit","hooks":[{"type":"command","command":"./.claude/hooks/format.sh","blocking":false}]}]}}关键点:
matcher:用于筛选工具(通常写正则/或模式串),比如Write|Edit、Bash。blocking:true:Hook 失败会阻断当前动作(适合安全校验、门禁)false:Hook 失败不阻断(适合格式化、统计、通知)
5.2 Hook 能拿到什么上下文?
命令型 Hooks 通常可以通过环境变量拿到上下文(不同事件变量略有差异,常见如):
| 变量名 | 说明 |
|---|---|
| TOOL_NAME | 工具名称(如 Write/Edit/Bash) |
| TOOL_INPUT | 工具输入(JSON 字符串) |
| TOOL_INPUT_FILE_PATH | 文件路径(Write/Edit 常用) |
| TOOL_OUTPUT | 工具输出(PostToolUse 常用) |
| USER_MESSAGE | 用户输入(UserPromptSubmit) |
| CLAUDE_WORKING_DIR | 当前工作目录 |
| NOTIFICATION_MESSAGE | 通知内容(Notification,JSON) |
更稳妥的写法:脚本里同时支持读取 stdin(JSON)与环境变量,避免某些环境差异导致取不到值。5.3 退出码怎么控制“阻断/不阻断”?
常见约定(建议你按这个写,团队最好统一):
exit0# 成功,继续exit2# 阻断错误(blocking=true 时会拦下本次操作)exit1# 非阻断错误(记录/提示,但不中断主流程)6. 提示型 Hooks
让 Claude 在“停下前”做质量门禁
提示型 Hooks 仅用于 Stop / SubagentStop:当 Claude 觉得“差不多可以结束了”,会先跑一次这个提示,让它自检并输出 JSON 决策。
6.1 基础语法
{"hooks":{"Stop":[{"hooks":[{"type":"prompt","prompt":"检查是否满足停止条件。返回 JSON: {\"decision\":\"stop\"|\"continue\",\"feedback\":\"说明\"}"}]}]}}5.2 响应格式(核心)
{"decision":"continue","feedback":"请先运行测试并修复 lint。"}你可以用它来实现 “Claude 自己给自己验收” 的体验,不满足条件就继续把事情做完,满足才停。
7. 案例模板
下面的模板尽量遵循一个原则:高风险校验 blocking=true,耗时工作 blocking=false。
依赖提示:后面的脚本示例会用到jq来解析 JSON(macOS 可用brew install jq)。
7.1 案例:SessionStart 做环境体检(不阻断)
session-init.sh:检查 Node/Python/Git 状态、依赖是否安装等(按你的项目改)。.claude/settings.json配置如下:
{"hooks":{"SessionStart":[{"hooks":[{"type":"command","command":"./.claude/hooks/session-init.sh","blocking":false}]}]}}session-init.sh 示例代码如下:
#!/usr/bin/env bashset -euo pipefail echo"🚀 Claude Code SessionStart"echo"cwd: ${CLAUDE_WORKING_DIR:-$(pwd)}"ifcommand -v git>/dev/null 2>&1&&git rev-parse --git-dir >/dev/null 2>&1;thenecho"git branch: $(git branch --show-current)"changes="$(git status --porcelain |wc -l |tr -d ' ')"if[["${changes}"!="0"]];thenecho"⚠️ uncommitted changes: ${changes}"fifiif[[ -f package.json ]];thencommand -v node>/dev/null 2>&1&&echo"node: $(node --version)"[[ -d node_modules ]]||echo"📦 tip: run npm install"fiif[[ -f requirements.txt ]];thencommand -v python >/dev/null 2>&1&&echo"python: $(python --version 2>&1|head -n 1)"[[ -n "${VIRTUAL_ENV:-}"]]||echo"🐍 tip: activate venv"fi7.2 案例:PreToolUse 拦截危险 Bash(阻断)
guard-bash.sh思路:从TOOL_INPUT(或 stdin JSON)里取出command,匹配rm -rf、git push --force等高危模式,命中就exit 2并在 stderr 输出原因。
{"hooks":{"PreToolUse":[{"matcher":"Bash","hooks":[{"type":"command","command":"./.claude/hooks/guard-bash.sh","blocking":true}]}]}}guard-bash.sh 示例代码如下:
#!/usr/bin/env bashset -euo pipefail input_json="${TOOL_INPUT:-}"if[[ -z "${input_json}"]];theninput_json="$(cat||true)"ficmd="$(echo"${input_json}"| jq -r '.command // empty'2>/dev/null ||true)"if[[ -z "${cmd}"]];thenexit0fiif[["${cmd}"== *"[claude-allow-danger]"* ]];thenexit0fideny_patterns=('(^|[[:space:]])rm[[:space:]]+-rf[[:space:]]+(/|~|\\$HOME)([[:space:]]|$)''(^|[[:space:]])git[[:space:]]+push([[:space:]].*)?--force''(^|[[:space:]])git[[:space:]]+reset([[:space:]].*)?--hard''(^|[[:space:]])chmod[[:space:]]+-R[[:space:]]+777')forpatternin"${deny_patterns[@]}";doifecho"${cmd}"|grep -Eq "${pattern}";thenecho"❌ 已阻断高危命令(PreToolUse):">&2echo" ${cmd}">&2echo"如确认要执行,请在命令末尾加 [claude-allow-danger] 作为显式确认。">&2exit2fidoneexit07.3 案例:PreToolUse 做敏感信息检测(阻断)
适用场景:Write/Edit 写入内容前,扫描是否疑似 Key/密码/Token,命中直接拦截。
{"hooks":{"PreToolUse":[{"matcher":"Write|Edit","hooks":[{"type":"command","command":"./.claude/hooks/check-secrets.sh","blocking":true}]}]}}check-secrets.sh示例代码如下:
#!/usr/bin/env bashset -euo pipefail input_json="${TOOL_INPUT:-}"if[[ -z "${input_json}"]];theninput_json="$(cat||true)"ficontent="$(echo"${input_json}"| jq -r '.new_string // .content // ""'2>/dev/null ||true)"patterns=('AKIA[0-9A-Z]{16}'# AWS Access Key'ghp_[a-zA-Z0-9]{36}'# GitHub PAT'sk-[a-zA-Z0-9]{32,}'# API Key(示例)'(?i)password\\s*[:=]\\s*["\x27][^"\x27]+["\x27]''(?i)api[_-]?key\\s*[:=]\\s*["\x27][^"\x27]+["\x27]')forpatternin"${patterns[@]}";doifecho"${content}"|grep -Pq "${pattern}"2>/dev/null;thenecho"❌ 检测到潜在敏感信息,已阻断写入(PreToolUse)">&2echo"pattern: ${pattern}">&2echo"建议:改用环境变量/密钥管理服务,不要把 Key 写进仓库。">&2exit2fidoneexit07.4 案例:PostToolUse 自动格式化 + 自动跑测试(不阻断)
{"hooks":{"PostToolUse":[{"matcher":"Write|Edit","hooks":[{"type":"command","command":"./.claude/hooks/format.sh","blocking":false},{"type":"command","command":"./.claude/hooks/auto-test.sh","blocking":false}]}]}}format.sh 示例代码如下:
#!/usr/bin/env bashset -euo pipefail file="${TOOL_INPUT_FILE_PATH:-}"[[ -n "${file}"&& -f "${file}"]]||exit0case"${file}"in *.ts|*.tsx|*.js|*.jsx|*.json)command -v prettier >/dev/null 2>&1&& prettier --write "${file}">/dev/null 2>&1||truecommand -v eslint >/dev/null 2>&1&& eslint --fix "${file}">/dev/null 2>&1||true;; *.py)command -v black >/dev/null 2>&1&& black "${file}">/dev/null 2>&1||truecommand -v isort >/dev/null 2>&1&& isort "${file}">/dev/null 2>&1||true;; *.go)command -v gofmt >/dev/null 2>&1&& gofmt -w "${file}">/dev/null 2>&1||true;; *.rs)command -v rustfmt >/dev/null 2>&1&& rustfmt "${file}">/dev/null 2>&1||true;;esacexit0auto-test.sh 示例代码如下:
#!/usr/bin/env bashset -euo pipefail file="${TOOL_INPUT_FILE_PATH:-}"[[ -n "${file}"&& -f "${file}"]]||exit0ifecho"${file}"|grep -Eq '(\\.test\\.|\\.spec\\.|/tests?/|__tests__)';thenexit0fiif[[ -f package.json ]]&&command -v npm>/dev/null 2>&1;thenifecho"${file}"|grep -Eq '\\.(ts|tsx|js|jsx)$';thenecho"🧪 running minimal js/ts tests (best effort)"npmtest --silent 2>/dev/null ||truefifiif[[ -f pyproject.toml || -f requirements.txt ]]&&command -v pytest >/dev/null 2>&1;thenifecho"${file}"|grep -Eq '\\.py$';thenecho"🧪 running minimal python tests (best effort)" pytest -q 2>/dev/null ||truefifiexit0建议:
- 格式化脚本按后缀选择
prettier/black/gofmt/rustfmt - 测试脚本“只跑与当前文件相关的最小集合”,避免每次都全量测试拖慢体验
7.5 案例:Notification 做系统通知(不阻断)
notify.sh(macOS)可以用osascript或terminal-notifier,从NOTIFICATION_MESSAGE里取title/message发系统通知。
{"hooks":{"Notification":[{"hooks":[{"type":"command","command":"./.claude/hooks/notify.sh","blocking":false}]}]}}notify.sh 示例脚本如下:
#!/usr/bin/env bashset -euo pipefail msg_json="${NOTIFICATION_MESSAGE:-}"if[[ -z "${msg_json}"]];thenmsg_json="$(cat||true)"fititle="$(echo"${msg_json}"| jq -r '.title // "Claude Code"'2>/dev/null ||echo"Claude Code")"message="$(echo"${msg_json}"| jq -r '.message // empty'2>/dev/null ||true)"[[ -n "${message}"]]||exit0ifcommand -v terminal-notifier >/dev/null 2>&1;then terminal-notifier -title "${title}" -message "${message}" -group "claude-code">/dev/null 2>&1||trueexit0fi osascript -e "display notification \"${message}\" with title \"${title}\"">/dev/null 2>&1||trueexit08. 常见问题
问题一:Hook 不执行
可以通过如下方式排查:
开调试:
claude --debug="hooks"检查脚本可执行权限:
chmod +x ./.claude/hooks/*.sh 脚本自测(用 stdin 模拟 JSON):
echo'{"sessionId":"test"}'| ./.claude/hooks/session-init.sh 问题二:无故被阻断
blocking=true 的 Hook 一定要做到:
- 只在命中明确条件时才
exit 2 - stderr 输出清晰原因(让 Claude 能把“为什么被拦”解释给你)
问题 三:变慢很明显
- 把耗时任务(全量测试、扫描整个仓库)改成
blocking=false或改成“增量/抽样” - 在脚本里加超时/短路条件(比如只对特定目录、特定后缀执行)
9. 文末
通过阅读本文,相信大家已经系统理解了 Claude Code 中 Hooks 的整体设计与实战价值:它并不是零散的脚本技巧,而是一套事件驱动的自动化与质量门禁机制。通过在 Session、Tool 执行前后以及 Stop 阶段合理配置命令型与提示型 Hooks,可以将格式化、测试、危险命令拦截、敏感信息校验等“重复但关键”的操作交由系统自动完成,把人为自觉升级为流程约束。
希望本文能对大家深入理解和落地使用 Claude Code Hooks 有所帮助,也欢迎在评论区分享你的实战配置、踩坑经验与优化思路。感谢阅读,本文完!