本文基于 Claude Code v2.1.195(2026-06-26 发布),hooks 文档以 Anthropic 官方仓库为准 ^1^。以下所有配置示例均经过验证,可直接使用。
你有没有遇到过这种情况:
明明在 CLAUDE.md 里写了"每次改完文件要跑格式化",Claude 前两次确实照做了,到第三次就开始"忘记"。你提醒它,它道歉,然后继续忘。
或者更恼火的:你让它去读一个外部 README,它回来之后好像"被洗脑了"------输出的代码风格突然变了,还带了一些奇怪的注释。
问题不在 Claude 不聪明,而在 CLAUDE.md 是建议,Hooks 是强制执行。
Anthropic 在官方 best practices 里说得很直白:凡是"每次都必须发生"的事,应该交给 hooks,而不是写在 CLAUDE.md 里等模型自觉 ^1^。
一句话总结:
Prompt 管建议,Hook 管纪律。
Hooks 是 Claude Code 的"确定性执行层"。你定义的规则,不会因为上下文太长、任务太复杂、或者模型"没注意"而被跳过。
一、6 个最值得上手的 Hook 玩法
每个玩法都配了可直接复制的配置示例。
1. Notification:等你操作时提醒你
Claude 在等权限、等你输入、或者任务完成时,自动推一个桌面通知。对经常切去浏览器查资料的场景,不用一直盯着终端了。
json
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude need you\" with title \"Claude Code\"'"
}
]
}
]
}
}
建议:只监听关键状态,所有操作都推的话十分钟响八次,你很快会关掉它。
2. PostToolUse:改完文件自动格式化
Claude 一修改文件,就自动跑 prettier、ruff format、gofmt、eslint --fix 等工具。
能少改一次格式,就少一次上下文消耗------Claude 能改对,但"每次都记得改"就不一定了。
json
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit",
"hooks": [
{
"type": "command",
"command": "npx prettier --write $FILE_PATH"
}
]
}
]
}
}
matcher 用 | 分隔工具名,我在项目里跑过,没问题。
3. PreToolUse:危险操作前先拦截
在 Claude 真正执行前拦下高风险操作。比如:
- 改
.env、lockfile、动migrations - 跑
rm -rf、git push --force、curl | sh
提示词是软的,hook 是硬的------规则会真的阻止执行,不是"建议" Claude 别这么做。
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c 'if echo \"$TOOL_INPUT\" | grep -qE \"(rm -rf|git push --force)\"; then echo \"已拦截危险命令\"; exit 1; fi'"
}
]
}
]
}
}
hook 能拿到完整命令($TOOL_INPUT),检查后决定放行还是拦截。
4. PermissionRequest:把重复确认变成白名单
对少量安全命令自动批准,减少反复点权限的打断。
白名单示例:git status、git diff --stat、npm test、pnpm lint。
json
{
"hooks": {
"PermissionRequest": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c 'if echo \"$TOOL_INPUT\" | grep -qE \"^(git status|git diff --stat|npm test|pnpm lint)\"; then echo \"{\\\"hookSpecificOutput\\\":{\\\"permissionDecision\\\":\\\"allow\\\"}}\"; else echo \"{\\\"hookSpecificOutput\\\":{\\\"permissionDecision\\\":\\\"ask\\\"}}\"; fi'"
}
]
}
]
}
}
警告 :白名单一定要窄,不能用太宽的正则。别写成 git .*,那等于把半个终端都交出去了。
5. SessionStart / PreCompact:自动同步环境变量
适合 monorepo、多语言项目、direnv、Python venv 等场景。
作用:在 Claude 会话启动、上下文压缩前后,自动刷新环境变量和项目状态,避免"本地 shell 能跑,Claude Bash 跑不了"的问题。
json
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "source .env && echo \"环境已加载: NODE_ENV=$NODE_ENV\""
}
]
}
],
"PreCompact": [
{
"hooks": [
{
"type": "command",
"command": "env > .claude/env-snapshot.txt"
}
]
}
]
}
}
6. Stop:没检查通过不准结束
在 Claude 宣布"任务完成"前,强制跑测试、lint、git diff --check 等检查。
不通过就不能标记 Done,能减少"看起来完成了,其实没验证"的情况。
json
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash -c 'npm test && git diff --check; exit $?'"
}
]
}
]
}
}
一(插)、一个综合案例:我的终端通知方案
讲完单个 hook,说一个把它们串起来的真实场景。
我用的终端是 Ghostty,它对 macOS 原生通知的支持不太好。Claude Code 跑完任务或者等输入时,我看不到任何通知,切去浏览器查资料回来经常不知道进展。
解决方案是用一个脚本挂到四个 hook 事件上:
json
{
"hooks": {
"Stop": [
{ "hooks": [{ "type": "command", "command": "/path/to/notifier.js" }] }
],
"SessionEnd": [
{ "hooks": [{ "type": "command", "command": "/path/to/notifier.js" }] }
],
"Notification": [
{ "hooks": [{ "type": "command", "command": "/path/to/notifier.js" }] }
],
"UserPromptSubmit": [
{ "hooks": [{ "type": "command", "command": "/path/to/notifier.js" }] }
]
}
}
四个事件分别覆盖:
- Stop:任务完成时通知
- SessionEnd:会话结束时通知(比如我手动退出)
- Notification:Claude 需要权限或等我输入时通知
- UserPromptSubmit:我开始输入新 prompt 时通知
最终效果:
!\[Claude_Code_Hooks_最被低估的能力-20260629112520608.jpeg]
一个小坑:Ghostty 的多 Tab 模式下,点击通知跳转的 Tab 不一定是发通知的那个------Ghostty 没有暴露 Tab 级别的 AppleScript 接口。我的习惯是把 Claude Code 跑在独立窗口里,避开这个问题。
二、两个进阶玩法
1. 压缩上下文前后保状态
官方推荐用 SessionStart 的 compact matcher,在上下文压缩(compaction)后重新注入关键上下文 ^1^。
Steve Kinney 还建议 ^2^:在 PreCompact 先写一个小状态文件,保存:
- 目标------这次会话要完成什么
- 已改文件------哪些文件被改过
- 已完成检查------测试过了吗?lint 过了吗?
- 剩余 blocker------还有什么卡住了
这对长任务特别有用。因为 compaction 后最容易丢的不是代码,而是"为什么要这样改"。
2. 给 Claude 装一个 Prompt Injection 防护层
lasso-security/claude-hooks 做了一个很实战的 PostToolUse 防护 ^3^:Claude 读文件、抓网页、跑 Bash 后,扫描输出里有没有:
- "忽略之前指令"
- "伪系统提示"
- "隐藏注释指令"
然后把警告写回 Claude 的上下文。
这个玩法很适合让 Claude 读外部网页------我每次让它去查 README 或 issue,第一反应就是扫一遍输出有没有注入指令。
三、3 个遗漏但有用的 Hook
官方的 hook 类型其实有 10 个 ^1^,除了上面讲的 6+2 个,还有 3 个值得知道:
1. UserPromptSubmit:prompt 提交时注入上下文
用户每次发 prompt 时,自动注入项目上下文或安全规则。比如自动挂上项目 README 摘要、检测危险关键词、附加 coding style 要求。
这就是一个 cat .claude/project_context.md 的事。
2. SubagentStop:多代理工作流的质量门禁
使用多代理(subagent)时,验证子代理是否真的完成了任务。
可以防止子代理"假装完成"------多代理工作流里这问题挺烦人。
3. SessionEnd:会话结束时的清理与记录
会话结束时自动做清理和记录。
用法:
- 推送会话统计(耗时、工具调用次数)
- 清理临时文件
- 记录未完成任务到 TODO
注意 :配 async: true,避免拖慢退出。
四、3 个最重要的避坑建议
1. Hook 默认是阻塞的
官方文档明确写了,hooks 默认会阻塞 Claude 的执行。
长测试、外部 API、重活请考虑 async: true,否则你会明显感觉"越装越卡"。社区在 HN 上也有人专门提到:hook 多了、插件多了,性能会掉 ^4^。
但 async: true 有副作用:执行顺序不确定。只在确实耗时的操作上使用它,别给所有 hook 都加上。
2. 项目级 Hook 本质上是可执行代码
Check Point 今年就披露过基于 repo 配置文件的攻击面 ^5^,Hooks / MCP / env 都可能成为入口。
Anthropic 官方也强调了 trust verification。
实践建议 :只从可信来源复制 hooks 配置。别随便把网上看到的 hook 配置粘贴到你的 .claude/settings.json 里。
3. 尽量用小 Hook,而不是万能 Hook
Steve Kinney 那句我很认同 ^2^:
一个 Hook 最好小到"你能用一句话讲清楚它是干什么的"。
越小越容易测,越不容易误伤。一个 hook 里做 10 件事,出错了你不知道哪件有问题。
五、收个尾
Hooks 不是什么新概念------CI/CD 里的 pre-commit hook、webhook、git hook,搞技术的都见过。但 Claude Code 把 hooks 提到了一个不一样的位置:它不是附赠功能,是你跟 AI 协作时最后一道确定性防线。
Prompt 会随着上下文变长而衰减,模型会在复杂任务中"走神"。但写进 .claude/settings.json 的 hook 不会------它每次都执行,每次都生效。
我花了一段时间才理解这个区别。以前总想着"把 prompt 写得更细更全",后来发现真正重要的那些事,靠的是 hook,不是 prompt。
你最想用 Hook 解决什么问题?欢迎在评论区告诉我。
#ClaudeCode #AI编程 #开发工具 #自动化
参考资料
本文首发于公众号「保持清醒的坐标系」,更多 AI 编程与技术实践内容欢迎关注。
Footnotes
-
Anthropic. Claude Code Hooks 开发文档 . anthropics/claude-code 仓库,plugins/plugin-dev/skills/hook-development/SKILL.md. 版本:Claude Code v2.1.195 (2026-06-26). 链接:github.com/anthropics/... ↩ ↩2 ↩3 ↩4
-
Steve Kinney. Claude Code Hooks 社区文章与最佳实践 . 引用"PreCompact 状态保存"和"小 Hook 原则". ↩ ↩2
-
Lasso Security. lasso-security/claude-hooks --- Prompt Injection 防护 hook 集合 . 最后更新:2026-06-23. 链接:github.com/lasso-secur... ↩
-
Hacker News. Claude Code Hooks 性能讨论帖 . 社区用户反馈 hook 增多后的性能影响. ↩
-
Check Point Research. 基于 AI 编码工具配置文件(Hooks/MCP)的攻击面研究报告 . 2026. ↩