1. 为什么提示词管不住 AI 的手
用 Claude Code 写代码的人,大概率都经历过这种场面:你在 CLAUDE.md 里用加粗字体写了三遍「禁止执行 rm -rf」「不要动 .env 文件」,结果它在一个看似无关的重构任务里,顺手就把配置文件覆盖了。你回头翻对话记录,发现它确实「读」了你的规则,但在具体执行的那一刻,规则被抛到了脑后。
这不是模型不听话,而是提示词的本质决定的。提示词是自然语言,它进入的是模型的上下文窗口,参与的是「概率生成」。当任务链条变长、工具调用变多,早期写下的约束在注意力权重里会被稀释。换句话说,提示词是在「求」AI 配合,执行与否靠的是它的自觉性。
Hooks 换了一个思路:它不跟模型商量,而是在工具调用的流水线上装闸机。Claude Code 在执行任何工具(读写文件、跑 Bash、调用 MCP)之前和之后,都会先经过你配置的脚本。脚本用退出码说话——放行还是拦截,是代码层面的强制,不经过模型的理解和判断。这就是「管住 AI 的手」和「管住 AI 的嘴」的区别。
这篇内容聚焦 Claude Code 的 Hooks 机制与 settings.json 配置,同时演示如何通过 TaoToken 统一 Key 和 API 通道,让 Hooks 拦截、模型调用、额度管理走同一条链路。适合已经在用 Claude Code、想让 AI 操作边界真正生效的开发者。下面从配置到验证一步步来,配置片段可以直接复制。
2. TaoToken 前置:统一 Key 与 API 通道
在配 Hooks 之前,先把模型调用通道理顺。原因很实际:Hooks 脚本里如果要调用模型做二次判断(比如让一个小模型判断某条命令是否危险),或者你想在拦截后自动记录日志、触发另一个 Agent,都需要一个稳定的 API 入口。如果每个工具各配一套 Key,管理成本会迅速失控。
TaoToken 在这里扮演的是统一通道的角色。它提供兼容 Anthropic 风格的 API 端点,Claude Code 通过环境变量指向它即可。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api (这个地址不加 UTM 参数,直接用于配置)。
你需要准备三样东西,我把它叫做「三件套」:
| 配置项 | 说明 | 获取位置 |
|---|---|---|
| Base URL | API 请求基址 | https://taotoken.net/api |
| API Key | 身份凭证 | 控制台 API Keys 页面 |
| Model ID | 模型标识 | 模型列表或文档 |
API Key 在控制台的 API Keys 页面创建,地址是 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。创建后复制保存,它只显示一次。Model ID 可以参考接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite ,里面列出了当前可用的模型标识。
配置方式有两种。第一种是环境变量,适合临时验证:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_API_KEY="sk-你的key" export ANTHROPIC_MODEL="你的Model ID"第二种是写进 Claude Code 的配置文件,适合长期使用。Claude Code 读取的配置路径通常在用户目录下的.claude/settings.json,项目级则在项目根目录的.claude/settings.json。这里要注意区分:模型通道配置和 Hooks 配置可以放在同一个 settings.json 里,但作用域不同。
如果你用的是 Claude Code 的 coding plan 模式,或者想长期跑 Agent 任务,可以了解 Coding Plan 方案 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite ,它在额度管理上更适合高频调用场景。
配好之后先别急着写 Hooks,用一次简单请求确认通道是通的。打开模型对话页面 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 发一条测试消息,能正常返回就说明 Key 和 Base URL 没问题。这一步很重要,因为后面 Hooks 脚本如果调用 API 失败,你会分不清是 Hooks 配错了还是通道没通。
3. 可复制的 settings.json 配置片段
现在进入核心部分。Claude Code 的 Hooks 配置写在 settings.json 里,结构是「事件名 → 匹配器 → 执行命令」。一个完整的 Hooks 配置包含三个要素:事件名决定在哪个时机触发,匹配器决定哪个工具会触发,执行命令决定触发后跑什么脚本。
先看项目级配置的完整片段,路径是项目根目录下的.claude/settings.json:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的key", "ANTHROPIC_MODEL": "你的Model ID" }, "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "python3 .claude/hooks/guard_bash.py" } ] } ], "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "npx prettier --write $CLAUDE_FILE_PATH" } ] } ], "SessionStart": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "cat .claude/rules.md" } ] } ] } }这段配置做了三件事。PreToolUse 匹配 Bash 工具,在 AI 执行任何 shell 命令之前,先跑guard_bash.py脚本做危险词检查。PostToolUse 匹配 Edit 和 Write,在文件被修改后自动跑 prettier 格式化。SessionStart 匹配所有情况,会话开始时把项目规则文件内容注入上下文。
匹配器支持正则,Edit|Write表示匹配 Edit 或 Write 任一工具。*表示匹配全部。事件名官方有三十多个,先吃透 PreToolUse、PostToolUse、SessionStart 这三个,九成场景够用。
再看全局级配置,路径是用户目录下的.claude/settings.json。它的结构和项目级一样,区别是作用范围覆盖所有项目。适合放通用规则,比如全局的危险命令拦截:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "python3 ~/.claude/hooks/global_guard.py" } ] } ] } }还有一个settings_local.json,用于本地实验,不会进 git 仓库。如果你在调试 Hooks 脚本,建议先写在这里,确认没问题再挪到 settings.json。
关于配置生效范围,记住这个优先级:项目级 settings.json 覆盖全局级,settings_local.json 覆盖项目级。改完配置需要重新开一个会话才能生效,保存后新开对话才会加载。可以用/hooks命令查看当前加载了哪些 Hooks,这是排查配置是否生效的第一手段。
脚本本身怎么写?以guard_bash.py为例,核心逻辑是读取标准输入的操作信息,检查是否包含危险词,用退出码给结论:
#!/usr/bin/env python3 import sys import json DANGEROUS = ["rm -rf", "DROP TABLE", "> /dev/sda", "mkfs", "dd if="] def main(): raw = sys.stdin.read() try: payload = json.loads(raw) except json.JSONDecodeError: sys.exit(0) command = payload.get("tool_input", {}).get("command", "") for word in DANGEROUS: if word in command: print(f"拦截:命令包含危险词 '{word}'", file=sys.stderr) sys.exit(2) sys.exit(0) if __name__ == "__main__": main()退出码 0 放行,退出码 2 拦截并把 stderr 内容反馈给 AI。这个脚本本质上只做一件事:看到危险词就喊停。你可以按项目需要扩充 DANGEROUS 列表,或者加入白名单逻辑。
4. 验证请求与成功结果
配置写完,必须验证。不验证的 Hooks 等于没配。验证分三步:确认加载、确认触发、确认拦截生效。
第一步,确认加载。在 Claude Code 里输入/hooks,它会列出当前会话加载的所有 Hooks。你应该能看到 PreToolUse、PostToolUse、SessionStart 三个事件及其对应的匹配器和命令。如果某个没出现,检查 settings.json 的 JSON 格式是否正确——一个多余的逗号就会导致整个文件解析失败,而且 Claude Code 不一定给明显报错。
第二步,确认触发。让 Claude Code 执行一条安全命令,比如echo hello。观察终端输出,如果 PreToolUse 的脚本被调用,你会看到脚本的执行痕迹(可以在脚本里加一行print("guard triggered", file=sys.stderr)做调试)。这一步验证的是「事件触发 → 匹配器对上 → 脚本执行」这条链路是通的。
第三步,确认拦截。让 Claude Code 执行一条包含危险词的命令,比如rm -rf /tmp/test_dir。预期结果是:命令没有真正执行,AI 收到拦截反馈,终端显示你脚本里写的报错信息。如果命令照常执行了,说明退出码没生效,检查脚本是否正确sys.exit(2),以及 stderr 是否有输出。
一个成功的验证结果长这样:
$ claude > 帮我删除 /tmp/test_dir 目录 [PreToolUse] guard_bash.py triggered 拦截:命令包含危险词 'rm -rf' AI: 我尝试执行删除操作,但被 Hooks 拦截了。命令包含危险词 'rm -rf', 需要你确认是否真的要执行。注意 AI 的反馈——它收到了拦截信息,并且会把这个信息纳入后续决策。这就是 Hooks 比提示词强的地方:不是「请求」它别做,而是「物理上」让它做不了,同时把原因告诉它。
PostToolUse 的验证更简单:让 AI 修改一个文件,然后检查文件是否被自动格式化。SessionStart 的验证是看新会话开始时,规则文件内容是否出现在上下文里。
如果你在验证时发现 API 调用报错,比如 401 或连接失败,先回到第 2 步确认 TaoToken 通道是通的。Hooks 脚本里如果调用了模型 API,Key 和 Base URL 必须和 settings.json 里的 env 一致。
5. 本篇常见错排查
配 Hooks 踩坑是常态,下面按真实报错分类整理。
401 错误。表现是模型调用返回401 Unauthorized。原因通常是 API Key 没配、配错,或者环境变量没生效。检查顺序:先看 settings.json 的 env 里ANTHROPIC_API_KEY是否填了正确的 Key,再看是否有 shell 环境变量覆盖了它。如果你在 Hooks 脚本里硬编码了 Key,确认那个 Key 和 settings.json 里的是同一个。Key 在控制台 API Keys 页面 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 重新生成后,旧 Key 会失效,记得同步更新。
local proxy failed。这个报错通常出现在网络层,表示 Claude Code 无法连接到配置的 Base URL。检查ANTHROPIC_BASE_URL是否写成了https://taotoken.net/api,注意结尾不要多加斜杠,也不要漏掉/api。如果你在 Hooks 脚本里用 curl 调 API,同样检查这个地址。
reading choices 相关报错。这类报错一般出现在解析模型返回结构时,说明返回的 JSON 格式和预期不符。常见原因是 Model ID 填错了,导致请求打到了不存在的模型。回到接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 核对 Model ID 的准确拼写。另外检查请求头里的anthropic-version是否缺失。
OAuth 相关报错。如果你之前用 OAuth 方式登录过 Claude Code,配置里可能残留了 OAuth token,和 API Key 方式冲突。解决方法是清理旧的凭证缓存,确保 settings.json 里只保留 API Key 方式。三件套(Base URL + Key + Model ID)必须同时存在且一致,缺一个都会出问题。
Hooks 不触发。配置改完没重新开会话是最常见原因。其次检查 JSON 格式,可以用python3 -m json.tool .claude/settings.json验证语法。再检查匹配器是否写对,Bash和bash是区分大小写的。最后确认脚本有可执行权限,chmod +x .claude/hooks/guard_bash.py。
脚本退出码不生效。确认脚本用的是sys.exit(2)而不是return 2,确认 stderr 有输出(拦截信息通过 stderr 反馈给 AI)。如果脚本抛异常退出,退出码可能是 1,不会触发拦截。在脚本外层加 try/except 兜底。
CC Switch / Cline MCP / Codex auth.json 场景。如果你同时用多个工具,注意每个工具的配置文件路径不同。CC Switch 有自己的配置入口,Cline MCP 在 MCP 配置里指定 Base URL 和 Key,Codex 的 auth.json 在~/.codex/auth.json。无论哪个工具,三件套都要写全:Base URL 用https://taotoken.net/api,Key 用控制台生成的,Model ID 用文档里核对的。少一个就会出现上面某类报错。
排查的核心思路是分层:先确认通道通(模型对话页面能返回),再确认配置加载(/hooks能看到),最后确认脚本逻辑(手动跑脚本测试退出码)。一层层排除,比盲目改配置快得多。
6. 把 AI 的操作边界交给代码
回到开头的问题:提示词管的是想法,Hooks 管的是行为。提示词进入上下文,参与概率生成,会被稀释;Hooks 进入执行流水线,用退出码强制拦截,不经过模型判断。两者不是替代关系,而是分工——提示词告诉 AI「应该怎么做」,Hooks 确保 AI「不能怎么做」。
实际用下来,最有价值的三个时机是 PreToolUse 拦截危险命令、PostToolUse 自动善后、SessionStart 注入规范。把这三个配好,AI 的操作边界就从「靠自觉」变成了「靠代码」。你可以从最简单的危险词拦截开始,跑通验证流程,再逐步扩充脚本逻辑。
配置过程中,TaoToken 的统一通道让模型调用和 Hooks 脚本共享同一套 Key 和 Base URL,省去了多工具多 Key 的管理麻烦。需要创建 Key 就去控制台 https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite ,需要核对模型标识就看接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite ,想先验证通道是否通就用模型对话 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 发一条测试消息。
最后给一个实用技巧:Hooks 脚本先写日志再写拦截逻辑。在脚本开头加一行把 payload 追加写入/tmp/claude_hooks.log,这样每次触发你都能看到 AI 到底传了什么参数进来。调试阶段这个日志比任何文档都管用,等逻辑稳定了再删掉。