Claude Code 完整指南(四):Hooks(自动化事件触发)

文章目录

1. 引言

在这里插入图片描述

在前面的博客,博主已经讲解了 Claude Code 相关的概念,有兴趣的同学可以参考下:

如果你已经在用 Claude Code 写代码,你一定遇到过这些“重复但必要”的动作:

  • 写完文件要跑格式化、lint、单测
  • 运行危险 Bash 之前想要一层保险(尤其是 rm -rfgit push --force
  • 想在会话开始时自动检查环境(Node/Python 版本、依赖是否安装)
  • 想在 Claude “准备停下”时强制做一次自检:测试过了吗?有未提交的改动吗?

Hooks 的价值就在于把这些动作从“ 你记得做” 变成 “系统自动做” ,并且可以做到“该阻断就阻断、该后台跑就后台跑、该提醒就提醒”

2. Hooks 是什么?

Hooks 是 Claude Code 的事件驱动自动化系统:在特定事件触发时,自动执行一组 Hook。

它主要有两种形态:

  1. 命令型 Hooks(command):运行 Shell 命令/脚本,适合自动化与校验(格式化、检查、通知、跑测试……)。
  2. 提示型 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工具执行后命令型自动格式化、自动测试、生成变更摘要
StopClaude 决定停止前提示型质量门禁(lint/test/git status)
SubagentStop子代理停止前提示型子任务验收(比如“测试是否补齐”)
NotificationClaude 发送通知时命令型转发通知(系统通知/Slack/飞书等)

5. 命令型 Hooks

写脚本就能自动化

5.1 基础语法(最常用)

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

关键点:

  • matcher:用于筛选工具(通常写正则/或模式串),比如 Write|EditBash
  • 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"fi

7.2 案例:PreToolUse 拦截危险 Bash(阻断)

guard-bash.sh 思路:从 TOOL_INPUT(或 stdin JSON)里取出 command,匹配 rm -rfgit 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] 作为显式确认。">&2exit2fidoneexit0

7.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 写进仓库。">&2exit2fidoneexit0

7.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;;esacexit0

auto-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)可以用 osascriptterminal-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||trueexit0

8. 常见问题

问题一: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 有所帮助,也欢迎在评论区分享你的实战配置、踩坑经验与优化思路。感谢阅读,本文完!

Read more

Jsp技术入门指南【十四】实现基于MySQL+JDBC+JSP数据库验证的登录界面与登录跳转功能

Jsp技术入门指南【十四】实现基于MySQL+JDBC+JSP数据库验证的登录界面与登录跳转功能

Jsp技术入门指南【十四】实现基于MySQL+JDBC+JSP数据库验证的登录界面与登录跳转功能 * 前言 * 第一步:加入驱动包与Maven * 第二步、创建并导入web库 * 第三步、连接本地数据库的java代码 * 核心代码讲解 * 第四步、创建数据库 * 第五步、导入并修改JSP登录文件 前言 * 在之前的博客中,我们详细探讨了 JSTL 中 SQL 标签库和自定义标签库的基础用法,并展示了如何利用这些标签库实现 MySQL 数据库连接以及数据分页展示功能。 * 本文将继续深入,介绍如何基于 MySQL 数据库实现用户登录验证功能,包括登录界面设计、用户身份验证以及登录成功 / 失败后的页面跳转处理。 我的个人主页,欢迎来阅读我的其他文章 https://blog.ZEEKLOG.net/2402_83322742?spm=1011.2415.3001.5343 我的JSP知识文章专栏 欢迎来阅读指出不足 https:

By Ne0inhk
Java 大视界 -- Java 大数据在智能公交调度优化与准点率提升中的应用实践(416)

Java 大视界 -- Java 大数据在智能公交调度优化与准点率提升中的应用实践(416)

Java 大视界 -- Java 大数据在智能公交调度优化与准点率提升中的应用实践(416) * 引言: * 正文: * 一、传统公交调度的 3 大核心痛点(基于杭州公交 2023 年 Q1 数据) * 1.1 数据孤岛:调度中心 “看不见” 真实路况 * 1.2 调度被动:发班计划 “一刀切”,不贴合实际需求 * 1.3 准点率难监控:数据不准 + 无复盘 * 二、Java 大数据智能调度的技术架构(杭州公交实战版) * 2.1 架构整体设计 * 2.2 技术选型的 3 个核心考量(杭州公交实战经验) * 2.2.1

By Ne0inhk
Java 网络编程(二)—— TCP流套接字编程

Java 网络编程(二)—— TCP流套接字编程

TCP 和 UDP 的区别 在传输层,TCP 协议是有连接的,可靠传输,面向字节流,全双工 而UDP 协议是无连接的,不可靠传输,面向数据报,全双工 有连接和无连接的区别是在进行网络通信的时候,通信双方有没有保存对端的地址信息,即假设 A 和 B 进行通信,A 保存了 B 的地址信息,B 也保存了 A 的地址信息,此时双方都知道和谁建立了连接,这就是有连接的通信,在之前的 UDP 数据报套接字编程中就提到过 UDP 是无连接的,所以在发送数据报的时候要加上对端的信息,防止丢包。 可靠传输是通过各种手段来防止丢包的出现,而不可靠传输则没有做任何处理直接把数据报传输过去,但是可靠传输不意味着能 100% 把数据报完整无误地传输给对方,只是尽可能降低丢包发生的概率,并且可靠传输是要使用很多手段来保持的,所以付出的代价相比于不可靠传输要大。 面向字节流就是以字节为单位来进行数据的传输,面向数据报就是以数据报为单位进行数据的传输。 全双工就是通信的双发可以同时给对方发送数据,

By Ne0inhk

Trae java项目配置全局maven和jdk

** Trae java项目配置全局maven和jdk ** 依次打开:设置-开发环境-Maven-for-Java(或全局搜索Maven-for-Java配置) 找到以下设置,点击在settings.json中编辑 在出现的配置文件中,填入以下配置: {"maven.excludedFolders":["**/.*","**/node_modules","**/target","**/bin","**/archetype-resources"],"maven.settingsFile":"你本地文件地址,例如:E:\\****\\apache-maven-3.8.4\\conf\\settings.xml","workbench.colorTheme":"Default

By Ne0inhk