三个月。我就这样让 Claude Code hooks 躺在文档里没人看,同时抱怨 AI 生成的代码无视我的格式约定。我在一月份设置了 Claude Code,几乎立即开始用它发布功能,然后告诉自己"稍后再处理 hooks"。太典型了。
事实证明 hooks 不是锦上添花。它们是缺失的一块拼图,让 Claude Code 真正融入真实的代理工作流,而不是一个偶尔忽视 ESLint 的非常昂贵的自动完成。
Claude Code Hooks 实际上是什么(没有模糊抽象)
Hooks 是 Claude Code 在其自身操作的特定点触发的 shell 命令。把它们想象成生命周期事件,类似于 Git hooks(如果你写过 pre-commit 脚本的话),但连接到 AI 的工具使用循环而不是 Git 的提交管道。
目前有四种事件类型:
PreToolUse,在 Claude 调用任何工具前运行(文件编辑、bash 命令等)PostToolUse,在工具调用完成后运行Notification,在 Claude 发送通知时触发Stop,在 Claude 完成完整响应轮次时运行
你在项目的 .claude/ 文件夹内的 settings.json 文件中配置它们,或者在 ~/.claude/settings.json 全局配置(如果你想在所有地方都用)。每个 hook 都有一个 matcher(哪个工具或事件触发它)和一个 hooks 数组来存放要运行的 shell 命令。
你的 hook 输出会被反馈到 Claude 的上下文中。这最后一点是让它真正有趣的原因,而不仅仅是个花哨的 cron 工作。
为什么这不同于自己手动运行脚本
你可以在每次 Claude 编辑后手动运行 Prettier。我试过,大约两周,直到在一个截止日期的压力下忘记了,推送了一个有 47 个格式违规的 PR。Hooks 自动运行,在会话内运行,Claude 可以读取它们的输出。所以如果你的 linter 抛出警告,Claude 看到它并可以在同一会话中采取行动。这个反馈循环才是全部要点。
在 Seahawk 项目中真正有效的设置
我要在这里具体说明,因为大多数文章中你能找到的笼统"在配置中添加 hooks"的建议如果没有上下文就毫无用处。
在 Seahawk,我们工作的很大一部分是 WordPress 构建和 WooCommerce 定制。我们也为 headless 设置做 React 前端,自 2022 年以来一直大量使用 Next.js。我最终采用的 hook 配置解决了特定于这些堆栈的问题。
以下是我用于 Next.js 项目的 settings.json 结构:
`` { "hooks": { "PostToolUse": [ { "matcher": "Write|Edit|MultiEdit", "hooks": [ { "type": "command", "command": "npx prettier --write $CLAUDE_FILE_PATHS && npx eslint --fix $CLAUDE_FILE_PATHS" } ] } ], "Stop": [ { "matcher": ".*", "hooks": [ { "type": "command", "command": "npx tsc --noEmit 2>&1 | head -20" } ] } ] } } ``
PostToolUse hook 在 Claude 接触文件后立即触发 Prettier 和 ESLint。Stop hook 在每轮末尾运行 TypeScript 类型检查,并显示任何错误的前 20 行。Claude 读取该输出,如果有类型错误,它在我看到响应之前自行修复。
仅那个 TypeScript 检查上个月就为我节省了可能四小时,在一个金融科技仪表板项目中,客户设置了严格的 noImplicitAny。Claude 不断在实用函数中生成 any 类型。在我添加 Stop hook 后,它开始在同一轮内自我修正。
我在 WordPress / PHP 项目中使用的钩子
WordPress 是另一回事。显然没有 TypeScript,但 PHP_CodeSniffer 配合 WordPress 编码标准规则集能让一切保持理智。2022 年我有个初级开发者在 WooCommerce 项目上,两周没运行过 PHPCS。代码审查……不太愉快。
对于 PHP 繁重的项目,我的 PostToolUse 钩子运行:
`` vendor/bin/phpcs --standard=WordPress $CLAUDE_FILE_PATHS 2>&1 | tail -30 ``
我用一个 bash 命令的 PreToolUse 钩子来配套:
`` { "matcher": "Bash", "hooks": [ { "type": "command", "command": "echo 'Bash tool triggered' >> ~/.claude/audit.log && date >> ~/.claude/audit.log" } ] } ``
第二个纯粹是偏执。它把 Claude 运行的每个 bash 命令都写入审计日志。当你在实时预发布环境运行 Claude Code 时(是的,我做过,是的有点冒险),知道确切执行了哪些 shell 命令真的很安心。
用钩子退出代码阻止行为
文档的这部分花了我不少时间才找到。如果你的钩子以代码 2 退出,Claude Code 会把它视为阻止,不会继续执行工具调用。退出代码 0 表示成功,任何非零值(但不是 2)会把标准错误作为上下文反馈回来。
所以你可以写一个 PreToolUse 钩子来真正阻止 Claude 做某事。我在有 migrations/ 目录且不想让 Claude 自动触及的项目中用这个:
`` #!/bin/bash if echo "$CLAUDE_FILE_PATHS" | grep -q "migrations/"; then echo "Migrations folder is protected. Do not edit migration files autonomously." exit 2 fi exit 0 ``
那个脚本位于 .claude/hooks/guard-migrations.sh。当 Claude 尝试写入 migrations/ 下的任何内容时,它会被阻止并看到消息。然后它会让我先确认再继续。简单有效。
这种控制力才是区分"我多少信任这个 AI 处理我的代码库"和"我真的信任它"的关键。
值得借鉴的实际钩子模式
这些不是理论。每一个都来自具体的痛点。
- 文件编辑后自动运行测试。我在
PostToolUse钩子中运行 npx jest --testPathPattern=$CLAUDE_FILE_PATHS --passWithNoTests。它只运行 Claude 刚编辑的文件相关的测试,不是整个套件。快到不烦人的程度。 - 提交就绪的格式化快照。一个
Stop钩子运行git diff --stat并把摘要反馈给 Claude。它能看到整个会话中的确切变化,这帮助它在我要求时写出合理的提交消息。 - 环境变量安全检查。Write 上的
PreToolUse钩子,用 grep 搜索硬编码的密钥模式(看起来像 API 密钥或密码的东西)。如果发现可疑内容,退出代码2。我应该在 18 个月前就构建这个。 - 长任务通知钩子。当 Claude 发送通知(
Notification事件)时,我触发一个curl调用到 Pushover 端点,在手机上收到推送通知。当你启动大型重构去泡茶时真的很有用。 - Bash 前的 PHP 语法检查。在 WordPress 项目上,任何 bash 执行前进行快速
php -l $CLAUDE_FILE_PATHS。在语法错误破坏预发布服务器前捕获它们。
官方 Claude Code 钩子文档列出了钩子脚本内可用的所有环境变量的完整参考。值得收藏。
钩子无法解决的问题
诚实很重要。钩子不是解决 Claude 生成逻辑错误代码的方案。它们解决流程问题:格式化、linting、类型安全、测试覆盖。如果 Claude 误解了你的数据模型并构建了错误功能,再多的后编辑 linting 都抓不住。
钩子也会增加延迟。如果你的 Prettier + ESLint 通过需要四秒钟,每次文件编辑现在需要多花四秒。在一个有 200 次文件编辑的会话中,那是 13 分钟的等待。分析你的钩子命令。保持它们快速。我用 --fix 变体(原地修改文件)而不是仅报告变体,正是因为单次快速通过胜过慢通过加第二次纠正通过。
它们需要你提前思考项目的失败模式。如果 Claude 编辑错误的文件会怎样?哪些标准绝对必须执行?无论如何这种思考都有价值,但它确实意味着钩子对经验丰富的开发者比初学者更有帮助。
设置钩子:分步指南
对于从零开始的任何人:
- 如果项目根目录中不存在
.claude/文件夹,请创建一个。 - 添加一个
settings.json文件,其中包含你的钩子配置(结构如上所示)。 - 对于任何超过一行的代码,请编写一个单独的 shell 脚本(.claude/hooks/your-script.sh),chmod +x 它,然后从配置中调用它,而不是内联命令。
- 通过在你的项目中运行
claude并故意触发钩子条件来测试。读取会话上下文中返回的内容。 - 如果某些内容没有按预期触发,请检查
~/.claude/logs/中的钩子执行日志。
Anthropic 开发者文档涵盖了完整的设置模式。如果你在思考钩子如何融入更广泛的 AI 编码工作流,Simon Willison 的博客是我推荐给任何想更仔细思考真实项目中代理 AI 工具的人的地方。
我最初犯的一个错误:我把所有钩子放在全局 ~/.claude/settings.json 中,然后想知道为什么我的 PHP 钩子在 JavaScript 项目上触发了。项目级设置会覆盖全局设置。将特定技术栈的钩子放在项目的 .claude/settings.json 中,将全局配置保留给应该应用于所有地方的内容(如审计日志和通知钩子)。
FAQ
Claude Code 钩子在 Windows 上工作吗?
钩子命令在你的系统使用的任何 shell 中运行。在 Windows 上,默认是 PowerShell 或 CMD,这意味着 bash 风格的脚本无法原生工作。WSL2 是实际的解决方案。我在 macOS 和 Ubuntu 开发机上工作,所以我没有亲身经历过这个问题,但 Anthropic 的文档明确指出了 shell 依赖。
钩子能访问 Claude 的对话上下文吗?
不能直接访问。钩子作为 shell 命令运行,接收环境变量如 CLAUDE_FILE_PATHS 和 CLAUDE_TOOL_NAME,但不会获得完整的对话记录。它们能做的是向 stdout 写入输出,Claude 在钩子运行后将其作为上下文读取。
钩子会明显减慢我的 Claude Code 会话吗?
完全取决于你的钩子做什么。对单个文件进行 php -l 语法检查不到 100 毫秒。在每次文件编辑时运行完整的 Jest 套件会很烦人。保持单个钩子命令在两到三秒内,你几乎不会注意到延迟。
在生产环境中使用钩子安全吗?
我会反过来问这个问题:你在生产环境中直接运行 Claude Code 吗?如果是的话,钩子是你最不用担心的问题。在暂存环境中使用它们,使用 PreToolUse 阻止模式保护敏感目录,让 Claude 完全远离生产数据库。
项目级钩子和全局钩子有什么区别?
全局钩子位于 ~/.claude/settings.json 中,适用于你的机器上的每个 Claude Code 会话。项目级钩子位于特定项目内的 .claude/settings.json 中,仅在你在该项目中时触发。项目级设置在两者都定义相同事件时优先。
---
诚实总结:钩子不是很性感。没人会写一篇关于运行 Prettier 的 shell 脚本的美妙架构的博客文章。但它们决定了 Claude Code 是原型玩具还是你实际上信任用于客户工作的东西。我应该在第一天就设置它们。你可能也应该这样做。
