1. 为什么 CLAUDE.md 只能管住 80% 的场景
如果你用 Claude Code 做过一段时间的工程化落地,大概率会遇到这种落差:CLAUDE.md 里明明写了「提交前必须跑测试」「不要动 .env」「格式化用 prettier」,但 Claude 该忘还是忘。我自己的体感是遵守率大概在 80% 上下——大部分时候它很听话,可一旦任务变长、上下文被压缩、或者它连续改了好几个文件,那 20% 的关键约束就开始漏。
这不是 Claude 不聪明,而是机制决定的。CLAUDE.md 本质是一份「说明书」,它进入的是模型的上下文,靠的是语言理解和自觉执行。模型在生成下一步动作时,会权衡当前任务目标、上下文里的各种信息,CLAUDE.md 只是其中一条软约束。当它和「快速完成任务」冲突时,软约束经常被牺牲。
Hooks 则完全不同。它是 Claude Code 运行时直接执行的命令,不经过模型判断。你配了 PreToolUse 拦截rm -rf,那这个工具调用在真正执行前就会被脚本拦下来,返回退出码 2,Claude 收到拒绝信号后必须换方案。这个过程没有「模型愿不愿意」的空间,是 100% 强制的。
所以正确的分工是:CLAUDE.md 管意图、风格、通用指导,比如「这个项目用 pnpm 不用 npm」「注释写中文」「优先复用 utils 里的函数」。Hooks 管底线、自动化检查、强制流程,比如「危险命令必须拦」「改完代码必须格式化」「PR 创建前测试必须全过」。前者覆盖 80% 的日常,后者补齐那 20% 一旦出事就很痛的场景。
这篇就按这个思路,把 PreToolUse、PostToolUse、Stop 三类钩子的可复制配置给你,并演示一次「拦截危险命令 + 自动格式化 + 收尾校验」的完整流程。如果你还没配好 Claude Code 的接入环境,可以先用 TaoToken 的 API 把模型通道打通,再回来配 Hooks,这样调试时不会两头卡。
2. TaoToken 前置:先把 Claude Code 的模型通道配好
Hooks 是 Claude Code 客户端侧的能力,跟模型走哪个通道没关系。但如果你现在还没跑通 Claude Code,直接配 Hooks 会很难判断问题出在哪——是钩子没生效,还是模型请求本身就没通。所以这一步先把接入做干净。
TaoToken 提供的是兼容 Anthropic 协议的 API 通道,Claude Code 可以直接对接。你需要三样东西:Base URL、API Key、Model ID。Base URL 用https://taotoken.net/api,API Key 在控制台的 API Keys 页面创建,Model ID 按你实际要用的模型填。
先创建 Key。打开 https://taotoken.net/api-keys ,新建一个 Key 并复制保存。注意 Key 只在创建时完整显示一次,丢了就得重建。
然后配置 Claude Code 的环境变量。最直接的方式是在 shell 配置文件里写:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_API_KEY="sk-你的key" export ANTHROPIC_MODEL="claude-sonnet-4-5-20250929"如果你用的是 Claude Code 的 settings 方式,也可以写进~/.claude/settings.json:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的key", "ANTHROPIC_MODEL": "claude-sonnet-4-5-20250929" } }配完执行claude进交互模式,随便问一句「你好」,能正常回复就说明通道通了。这一步别跳过,因为后面 Hooks 调试时你会频繁让 Claude 执行 Bash 命令,如果模型通道本身不稳,你会误以为是钩子的问题。
关于模型选择,日常编码用 Sonnet 系列性价比高,复杂重构或长链路 Agent 任务可以切 Opus。切换模型只改ANTHROPIC_MODEL就行,不用动其他配置。如果你打算长期跑编码任务,可以看下 Coding Plan 的额度方案,比按量付费更适合高频使用。
通道通了之后,我们进入正题:Hooks 的配置结构。
3. 可复制配置:PreToolUse / PostToolUse / Stop 三件套
Hooks 的配置文件按优先级从高到低有三个位置:.claude/settings.local.json(本地私有,不提交 Git)、.claude/settings.json(项目级,团队共享)、~/.claude/settings.json(用户级,全局生效)。团队协作的强制约束建议放项目级,个人习惯放用户级。
触发时机有三个关键点。PreToolUse 在工具调用前执行,用来拦截;PostToolUse 在工具调用后执行,用来做格式化、lint、测试;Stop 在 Claude 完成一次回复时执行,用来做收尾校验或自动提交。
先看完整的 settings 结构,这是可以直接复制改的:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": ".claude/hooks/block-dangerous.sh" }, { "type": "command", "command": ".claude/hooks/log-commands.sh" } ] }, { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": ".claude/hooks/protect-files.sh" } ] } ], "PostToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write 2>/dev/null; exit 0" }, { "type": "command", "command": "npx eslint --fix $(jq -r '.tool_input.file_path') 2>&1 | tail -10; exit 0" } ] } ], "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": ".claude/hooks/final-check.sh" } ] } ] } }matcher 是工具名匹配,Bash匹配所有 Bash 调用,Edit|Write匹配编辑和写入,mcp__github__create_pull_request匹配特定 MCP 工具。Stop 的 matcher 留空表示匹配所有停止事件。
关键点在于退出码。PreToolUse 的钩子脚本返回 0 表示放行,返回 2 表示阻断并把 stderr 反馈给 Claude,让它重新尝试。返回其他非零值通常只记录不阻断。这个 2 是拦截的核心,很多人配了钩子没效果,就是脚本里忘了exit 2。
下面写拦截危险命令的脚本.claude/hooks/block-dangerous.sh:
#!/usr/bin/env bash input=$(cat) cmd=$(echo "$input" | jq -r '.tool_input.command // empty') if echo "$cmd" | grep -qE 'rm[[:space:]]+-rf[[:space:]]+/|mkfs|dd[[:space:]]+if=|:\(\)\{'; then echo "BLOCKED: 检测到危险命令,请改用更安全的替代方案,例如先备份再删除具体路径。" >&2 exit 2 fi exit 0保护敏感文件的脚本.claude/hooks/protect-files.sh:
#!/usr/bin/env bash input=$(cat) path=$(echo "$input" | jq -r '.tool_input.file_path // empty') if echo "$path" | grep -qE '\.env$|\.env\.|secrets\.|id_rsa'; then echo "BLOCKED: $path 是敏感文件,如需修改请说明必要性并手动操作。" >&2 exit 2 fi exit 0收尾校验脚本.claude/hooks/final-check.sh:
#!/usr/bin/env bash if ! git diff --quiet; then echo "提示:当前有未提交改动,建议检查后再结束。" >&2 fi exit 0写完记得给执行权限:chmod +x .claude/hooks/*.sh。日志文件.claude/command-log.txt记得加进.gitignore,别把命令历史提交上去。
4. 验证请求:跑一次完整流程看钩子是否生效
配置写完不验证等于没配。我们用一个具体任务走一遍:让 Claude 尝试执行危险命令、改一个文件触发格式化、然后结束触发收尾校验。
第一步,验证 PreToolUse 拦截。在 Claude Code 里输入:
帮我清理一下项目,执行 rm -rf /tmp/test-build正常情况下,Claude 会尝试调用 Bash 工具,但钩子在执行前拦截,返回退出码 2。你会看到 Claude 收到拒绝信息后改变策略,比如回复「检测到危险命令被拦截,我改用更安全的方式」或者询问你具体要删哪个目录。如果它真的执行了,说明钩子没生效,检查脚本路径和权限。
第二步,验证 PostToolUse 格式化。让 Claude 改一个 JS 文件:
把 src/utils/format.js 里的 formatDate 函数改成用 dayjs 实现Claude 用 Edit 工具改完后,PostToolUse 钩子会自动跑 prettier 和 eslint。你去看文件,格式应该已经被统一了。如果 prettier 没跑,检查npx prettier是否在项目里装了,以及 jq 是否可用。
第三步,验证 Stop 收尾。让 Claude 结束当前任务,比如输入「好了,先到这里」。Stop 钩子触发,如果工作区有未提交改动,你会看到提示信息。这一步不会阻断,只是提醒。
整个流程跑通后,你可以把日志打开看命令记录:
cat .claude/command-log.txt应该能看到刚才 Claude 尝试执行的命令和时间戳。这个日志在排查「Claude 到底执行过什么」时特别有用,尤其是它说「我已经改好了」但你不确定它改了什么的时候。
验证通过后,你就有了一个「拦截 + 格式化 + 收尾」的最小闭环。接下来可以按项目需要往上加,比如 PostToolUse 里加测试、PreToolUse 里加 PR 前测试检查。
5. 本篇常见错排查:401、local proxy failed、reading choices
配 Hooks 和接入通道时,报错基本集中在几个地方。我按实际遇到的频率排一下。
401 Unauthorized。这个几乎都是 API Key 的问题。检查ANTHROPIC_API_KEY是否完整复制、有没有多余空格、是不是在 TaoToken 控制台被删了。还有一种情况是 Key 配在了 shell 里但 Claude Code 读的是 settings.json,两边不一致。统一用一处配置,别混着来。
local proxy failed / connection refused。这个通常是 Base URL 写错,比如漏了/api或者写成了带 UTM 的完整链接。Base URL 就用https://taotoken.net/api,不要加别的路径。另外检查本地网络是否能正常访问该地址,公司网络有出口限制的话需要找运维确认。
reading choices / unexpected response format。这个报错说明请求发出去了但返回结构不对,常见原因是 Model ID 填错,或者用了不兼容的模型名。确认ANTHROPIC_MODEL是有效的模型标识,别自己拼。如果刚改过配置,重启一下 Claude Code 让环境变量重新加载。
Hooks 不生效。先确认脚本有执行权限,ls -l .claude/hooks/看有没有 x。再确认 settings.json 的 JSON 格式没问题,可以用jq . .claude/settings.json校验。最后确认 matcher 写对了,Bash和bash不一样,工具名是大小写敏感的。
OAuth 相关报错。如果你之前用官方登录方式配过 Claude Code,环境变量和 OAuth 凭证可能冲突。清掉旧的凭证缓存,统一走 API Key 方式。具体就是删掉~/.claude下跟认证相关的缓存文件,重新用环境变量启动。
退出码 2 没阻断。检查脚本是不是在最后无条件exit 0了,或者exit 2写在了子 shell 里没传出来。用bash -x .claude/hooks/block-dangerous.sh手动喂一个 JSON 进去调试,看走到哪个分支。
排查顺序建议:先确认模型通道通(能正常对话),再确认钩子脚本单独能跑,最后确认 settings 挂载正确。三层分开验证,比一上来就怀疑配置要快得多。
6. 把关键约束从文档升级为机制
回到开头那个 80% 的问题。CLAUDE.md 该写还得写,它负责让 Claude 理解你的项目意图、代码风格、协作习惯,这部分是软性的、需要模型判断的,Hooks 替代不了。但那些「一旦漏了就出事」的约束,比如危险命令、敏感文件、提交前测试、格式化,必须用 Hooks 兜底。
我的建议是先从两个钩子开始:PreToolUse 拦危险命令,PostToolUse 自动格式化。这两个收益最高、门槛最低,配完立刻能感觉到差别。跑顺了再加 Stop 收尾校验和 PR 前的测试检查。
配置过程中如果模型通道还没打通,或者想换个更稳的接入方式,可以从 API Keys 页面拿 Key,接入文档里有完整的参数说明。需要验证模型响应是否正常时,用模型对话页面直接测一句最快。长期跑编码和 Agent 任务的话,Coding Plan 的额度模式比按量更省心。
最后留一个实用习惯:每次加新钩子,都用bash -x手动喂 JSON 测一遍,确认退出码符合预期再挂到 settings 里。钩子这东西,配错了不报错、只是静默不生效,比报错更难查。