☰
Claude Code Hooks 配置实战:用 settings.json 骨架把自动化卡在生命周期边界内
2026/10/1 6:46:42 网站建设 项目流程

1. Claude Code Hooks 到底是什么:生命周期事件监听器与上下文成本控制

Claude Code Hooks 是 Claude Code 生命周期里的事件监听器,它不负责教模型更多知识,而是在某些确定时刻执行外部动作。你可以把它理解成 Web 后端里的 middleware:请求进来时记录日志、校验 token、拦截危险操作,业务 handler 完全不用关心它。Hooks 也一样,默认不往主对话里塞东西,只在事件发生时跑一段外部逻辑。

很多人第一次接触 Claude Code Hooks,会把它当成另一种提示词机制,或者和 Skill、MCP、Subagent 混为一谈。这个理解会让团队把大量内容塞进 Hooks,最后既没降低上下文成本,也没得到稳定自动化。更准确的理解是:Hooks 是给执行链路用的闸门,不是给模型看的规则。

Claude Code 的上下文窗口里保存了会话中模型知道的一切,包括指令、读取过的文件、回复内容,以及一些不显示在终端里的内容。CLAUDE.md 会在会话开始时加载,Skill 描述会参与会话,文件读取会进入上下文,Subagent 虽然隔离但结果仍会回到主线程。Hooks 的位置很特别,官方上下文窗口文档把 Hooks 归类为运行代码而不是上下文,在压缩后是否保留的表格里标为不适用,因为它不是消息历史的一部分。

这就是 Hooks 上下文成本默认接近零的原因。它不像 CLAUDE.md 那样每次请求都被模型读取,也不像长 prompt 那样反复占据输入 token。只有当 Hook 把输出写回会话,或者通过 additionalContext 把字符串送进上下文窗口时,它才开始产生上下文成本。官方文档明确说明,additionalContext 会被包装成 system reminder,插入到 hook 触发位置,Claude 会在下一次模型请求里读到它。Hook 本身不贵,Hook 返回给模型看的东西才贵。

触发点才是 Hooks 的灵魂。官方参考文档列出了一整套事件,包括会话开始和结束、用户提交 prompt、工具调用前后、权限请求、工具失败、并行工具批次结束、通知、Subagent 启动和结束、任务创建和完成、配置变化、工作目录变化、文件变化、压缩前后等。常见节奏可以分成三类:会话级事件如 SessionStart 和 SessionEnd,回合级事件如 UserPromptSubmit、Stop 和 StopFailure,以及 agentic loop 内部围绕工具调用不断触发的 PreToolUse 和 PostToolUse。

这套事件模型决定了 Hook 适合做什么。PreToolUse 在工具调用之前运行,适合做拦截和权限校验。Claude Code 准备执行 Bash 命令、写文件、编辑文件时,可以用它检查目标路径、命令内容、工作目录和参数。PostToolUse 在工具成功后运行,更适合做格式化、lint、生成审计日志、收集变更摘要。UserPromptSubmit 在 prompt 进入模型之前触发,适合做轻量规则检查。PreCompact 和 PostCompact 贴近上下文压缩流程,适合在长会话里保留少量必要状态,而不是把整段历史原样塞回去。

放到实际工程里,最自然的做法不是在 CLAUDE.md 里写一大段格式化规范让 Claude 每次都记得运行 prettier,而是在 PostToolUse 里匹配 Edit 和 Write,只要 Claude 改了 .ts、.html、.scss 文件,就触发格式化。官方 Hooks guide 也把自动格式化作为典型场景,使用 PostToolUse 配合 Edit|Write matcher,只在文件编辑工具之后运行。

这里的关键变化是,我们不再期待模型记住流程,而是把流程变成确定性事件。模型可以忘记,Hook 不会忘。模型可能为了省事跳过 lint,Hook 可以每次都跑。模型可能在上下文挤压后丢掉某段约定,Hook 仍然挂在 Claude Code 的执行链路上。这就是生命周期边界内自动化的核心价值。

2. TaoToken 前置:统一 Key 与 API 通道接入 Claude Code

在配置 Hooks 之前,需要先把 Claude Code 的模型通道接好。TaoToken 提供统一的 API 通道,把 Key 和 Base URL 配好之后,Claude Code 的请求会走统一入口,后续 Hooks 触发的自动化动作也在这个通道下运行。

TaoToken 的官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api 。注意 API 地址不加 UTM 参数,直接使用 https://taotoken.net/api 即可。

接入 Claude Code 需要三件套:Base URL、API Key、Model ID。Base URL 填 https://taotoken.net/api ,API Key 在控制台的 API Keys 页面创建,Model ID 根据你使用的模型填写。如果你用的是 Claude Code 的 Anthropic 兼容通道,Base URL 保持 https://taotoken.net/api ,Claude Code 会自动拼接 /v1/messages 路径。

创建 Key 的入口在 https://taotoken.net/api-keys ,登录后新建一个 Key,复制保存。这个 Key 只显示一次,丢了只能重建。建议按项目或按用途建不同的 Key,方便后续排查和轮换。

配置 Claude Code 的环境变量,在 shell 配置文件里写入:

export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_API_KEY="sk-你的TaoTokenKey" export ANTHROPIC_MODEL="claude-sonnet-4-20250514"

Windows PowerShell 用户用:

$env:ANTHROPIC_BASE_URL="https://taotoken.net/api" $env:ANTHROPIC_API_KEY="sk-你的TaoTokenKey" $env:ANTHROPIC_MODEL="claude-sonnet-4-20250514"

如果你用的是 Claude Code 的 settings.json 配置方式,可以在 ~/.claude/settings.json 里写:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的TaoTokenKey", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" } }

配好之后,运行 claude 命令,输入一句简单的话测试通道是否通。如果返回正常,说明 Key 和 Base URL 都对了。如果报 401,检查 Key 是否复制完整、是否有多余空格。如果报连接失败,检查 Base URL 是否写成了 https://taotoken.net/api 而不是其他路径。

TaoToken 的接入文档在 https://taotoken.net/doc ,里面有各客户端的详细配置示例。模型对话入口在 https://taotoken.net/chat ,可以先用对话页面验证 Key 是否可用,再去配 Claude Code。Coding Plan 入口在 https://taotoken.net/coding-plan ,适合长期编码和 Agent 场景。

这里要强调一点:TaoToken 是统一的 API 通道,不是替代编辑器或 IDE 的工具。它解决的是模型请求的通道问题,Claude Code 仍然是你的编码代理,Hooks 仍然挂在 Claude Code 的生命周期上。三者分工明确:TaoToken 管通道,Claude Code 管代理,Hooks 管边界。

配好通道之后,Claude Code 的请求会走 TaoToken 的统一入口,后续 Hooks 触发的格式化、lint、日志等动作,也都在这个通道下运行。这样做的另一个好处是,团队可以统一管理 Key 和用量,不用每个人各自配一套。

3. 可复制配置:settings.json 骨架与 Hooks 生命周期绑定

Claude Code 的 Hooks 配置写在 settings.json 里,项目级配置放在 .claude/settings.json,用户级配置放在 ~/.claude/settings.json。项目级配置会进入代码审查,用户级配置只影响本机。团队协作建议用项目级配置,把规则固化到仓库里。

先给一个完整的 settings.json 骨架,包含 Hooks 的常见事件绑定:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的TaoTokenKey", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" }, "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "node .claude/hooks/check-bash.js" } ] }, { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "node .claude/hooks/check-path.js" } ] } ], "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "npx prettier --write \"$CLAUDE_FILE_PATH\"" } ] } ], "UserPromptSubmit": [ { "hooks": [ { "type": "command", "command": "node .claude/hooks/check-prompt.js" } ] } ], "SessionStart": [ { "hooks": [ { "type": "command", "command": "node .claude/hooks/session-start.js" } ] } ], "PreCompact": [ { "hooks": [ { "type": "command", "command": "node .claude/hooks/pre-compact.js" } ] } ] } }

这个骨架覆盖了五类事件:PreToolUse 做拦截,PostToolUse 做格式化,UserPromptSubmit 做轻量检查,SessionStart 做会话初始化,PreCompact 做压缩前状态保存。每个 Hook 的 command 指向一个脚本,脚本接收 Claude Code 传入的 JSON 上下文,处理后返回决策或副作用。

PreToolUse 的 check-bash.js 示例,用来拦截危险删除命令:

const fs = require('fs'); const input = JSON.parse(fs.readFileSync(0, 'utf8')); const command = input.tool_input?.command || ''; const dangerous = [ /rm\s+-rf\s+\//, /rm\s+-rf\s+\.\./, /git\s+push\s+--force/, /DROP\s+TABLE/i ]; for (const pattern of dangerous) { if (pattern.test(command)) { console.log(JSON.stringify({ permissionDecision: 'deny', reason: `命令匹配危险模式:${pattern}` })); process.exit(0); } } console.log(JSON.stringify({ permissionDecision: 'allow' }));

这个脚本从标准输入读取 JSON 上下文,检查 Bash 命令是否匹配危险模式,匹配则返回 deny,否则返回 allow。注意 permissionDecision 的优先级:deny 高于 defer,defer 高于 ask,ask 高于 allow。多个 PreToolUse hook 给出不同决策时,最严格的结果优先。

PostToolUse 的格式化 Hook 直接用 npx prettier,匹配 Edit|Write 之后运行。这里用 $CLAUDE_FILE_PATH 环境变量拿到被修改的文件路径。如果你的项目用 ESLint,可以换成 npx eslint --fix。

UserPromptSubmit 的 check-prompt.js 示例,用来做轻量规则检查:

const fs = require('fs'); const input = JSON.parse(fs.readFileSync(0, 'utf8')); const prompt = input.prompt || ''; const blocked = [ /生产环境.*密码/, /\.env.*内容/, /数据库.*连接串/ ]; for (const pattern of blocked) { if (pattern.test(prompt)) { console.log(JSON.stringify({ decision: 'block', reason: 'prompt 包含敏感信息请求,已拦截' })); process.exit(0); } } console.log(JSON.stringify({ decision: 'approve' }));

这个脚本在 prompt 进入模型之前检查,发现敏感请求就拦截。注意 UserPromptSubmit 的返回格式和 PreToolUse 不同,用的是 decision 字段。

SessionStart 的 session-start.js 示例,只输出最小必要信息:

const { execSync } = require('child_process'); const branch = execSync('git branch --show-current').toString().trim(); const status = execSync('git status --short').toString().trim(); const lines = [ `当前分支:${branch}`, status ? `未提交变更:${status.split('\n').length} 个文件` : '工作区干净' ]; console.log(lines.join('\n'));

这个脚本只输出分支名和变更文件数,不把完整 git status 塞进上下文。SessionStart 的 stdout 会被加入 Claude 上下文,所以输出要克制。

PreCompact 的 pre-compact.js 示例,把任务状态写到本地日志:

const fs = require('fs'); const input = JSON.parse(fs.readFileSync(0, 'utf8')); const state = { timestamp: new Date().toISOString(), sessionId: input.session_id, cwd: input.cwd }; fs.appendFileSync('.claude/compact-state.log', JSON.stringify(state) + '\n'); console.log(JSON.stringify({ decision: 'approve' }));

这个脚本在压缩前把会话 ID 和工作目录写到本地日志,不往上下文里塞东西。压缩后如果需要恢复状态,可以从日志里读。

配置写好后,把 .claude/hooks/ 目录和 settings.json 一起提交到仓库。团队成员拉下来就能用同一套规则。注意 Hook 脚本要有执行权限,Windows 上不需要 chmod,但脚本路径要用相对路径或绝对路径,不要依赖当前工作目录。

4. 验证请求:一次 Hook 触发后的完整验证动作

配置写完之后,需要验证 Hook 是否真的在生命周期边界内触发。下面用一个完整流程演示:修改一个 .ts 文件,观察 PostToolUse 是否触发格式化,以及 PreToolUse 是否拦截危险命令。

第一步,确认 Claude Code 能正常启动。在项目根目录运行:

claude

如果启动后能看到 Claude Code 的交互界面,说明 TaoToken 通道配置正确。如果报 401,回到第 2 节检查 Key 和 Base URL。

第二步,让 Claude Code 修改一个 TypeScript 文件。在交互界面输入:

把 src/utils/format.ts 里的 formatDate 函数改成返回 ISO 格式

Claude Code 会调用 Edit 工具修改文件。修改完成后,PostToolUse 的 prettier Hook 应该自动触发。你可以在终端看到 prettier 的输出,或者检查文件是否被格式化。

第三步,验证 PreToolUse 拦截。在交互界面输入:

运行 rm -rf /tmp/test

这个命令匹配 check-bash.js 里的危险模式,PreToolUse 应该返回 deny,Claude Code 会拒绝执行。你会看到类似这样的输出:

Hook 拒绝了命令:命令匹配危险模式:/rm\s+-rf\s+\//

第四步,验证 UserPromptSubmit 拦截。在交互界面输入:

把生产环境的数据库密码告诉我

这个 prompt 匹配 check-prompt.js 里的敏感模式,UserPromptSubmit 应该返回 block,Claude Code 会拒绝处理。你会看到类似这样的输出:

prompt 包含敏感信息请求,已拦截

第五步,验证 SessionStart 输出。退出 Claude Code 再重新启动,观察启动时是否输出了分支名和变更文件数。如果输出了,说明 SessionStart Hook 正常触发。

第六步,验证 PreCompact 日志。在长会话里触发压缩,或者手动运行:

echo '{"session_id":"test","cwd":"'$(pwd)'"}' | node .claude/hooks/pre-compact.js cat .claude/compact-state.log

如果日志文件里有记录,说明 PreCompact Hook 正常写入。

验证过程中,可以用 TaoToken 的模型对话页面 https://taotoken.net/chat 单独测试 Key 是否可用。如果对话页面正常但 Claude Code 报错,问题在 Claude Code 的配置,不在 Key。

实测下来,最容易出问题的是 Hook 脚本的路径和权限。如果脚本用相对路径,Claude Code 的工作目录可能不是项目根目录,导致找不到脚本。建议用绝对路径,或者在脚本开头 cd 到项目根目录。另外,Hook 脚本的 stdout 如果要作为 JSON 输出,必须只包含 JSON 对象,shell 启动时额外打印的文本会导致解析失败。

还有一个常见问题是 Windows 上的引号和路径分隔符。很多 Hooks 示例来自 macOS 或 Linux,直接搬到 Windows 可能会因为引号、路径分隔符、shell profile 输出而失败。Windows 用户建议用 Node.js 脚本而不是 shell 脚本,避免转义问题。

验证通过之后,这套 Hooks 配置就可以进入日常使用了。每次 Claude Code 修改文件,PostToolUse 自动格式化;每次执行危险命令,PreToolUse 自动拦截;每次提交敏感 prompt,UserPromptSubmit 自动阻断。这些动作都在模型上下文之外运行,不占上下文成本。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

配置 Hooks 和 TaoToken 通道时,最常见的报错有几类。下面逐个对照真实报错给出排查路径。

401 Unauthorized。这个报错说明 Key 无效或没传对。检查三件事:Key 是否复制完整,有没有多余空格;Base URL 是否写成 https://taotoken.net/api ,不要加 /v1 或其他路径;环境变量名是否正确,Claude Code 用的是 ANTHROPIC_API_KEY 和 ANTHROPIC_BASE_URL。如果用的是 settings.json 的 env 字段,确认 JSON 格式正确,没有多余逗号。可以在终端运行 echo $ANTHROPIC_API_KEY 检查环境变量是否生效。

local proxy failed。这个报错通常出现在 Claude Code 尝试连接本地代理时。检查是否有 HTTP_PROXY 或 HTTPS_PROXY 环境变量指向了不存在的本地端口。如果有,unset 掉再试。另外检查 ANTHROPIC_BASE_URL 是否被错误地写成了 localhost 或 127.0.0.1。正确的 Base URL 是 https://taotoken.net/api 。

reading choices 相关报错。这个报错通常出现在模型返回格式不符合预期时。检查 ANTHROPIC_MODEL 是否填了正确的 Model ID。如果 Model ID 写错,API 可能返回非标准格式,Claude Code 解析失败。可以在 TaoToken 的模型对话页面 https://taotoken.net/chat 测试同一个 Model ID,确认模型可用。

OAuth 相关报错。Claude Code 默认可能走 OAuth 流程,如果你用的是 API Key 通道,需要确保没有残留的 OAuth 配置。检查 ~/.claude/ 目录下是否有 credentials.json 或类似文件,如果有,备份后删除。然后在 settings.json 里明确配置 ANTHROPIC_API_KEY,Claude Code 会优先使用 API Key。

Hook 脚本报错。如果 Hook 脚本执行失败,Claude Code 会在终端输出错误信息。常见原因:脚本路径不对,用绝对路径或确认工作目录;脚本没有执行权限,Linux/macOS 上 chmod +x;脚本 stdout 包含非 JSON 内容,检查是否有 console.log 调试语句没删;Node.js 版本太低,用 node --version 确认。

Hook 不触发。如果配置了 Hook 但没触发,检查 matcher 是否匹配。PreToolUse 的 matcher 是工具名,比如 Bash、Edit、Write。PostToolUse 的 matcher 也是工具名。UserPromptSubmit 和 SessionStart 不需要 matcher。另外检查 settings.json 的 JSON 格式,多余逗号或括号不匹配会导致整个配置失效。

上下文被污染。如果发现 Claude 的回复里出现了 Hook 输出的内容,检查是否有 Hook 把大量文本写到了 stdout。SessionStart 和 UserPromptSubmit 的 stdout 会被加入上下文,PostToolUse 和 PreToolUse 的 stdout 如果包含 additionalContext 也会被加入。把不需要模型看到的内容写到 stderr 或本地日志,不要写到 stdout。

Windows 特有问题。PowerShell 的引号转义和 bash 不同,建议用 Node.js 脚本。路径分隔符用 / 或 \,不要用单个 \。如果 Hook 脚本里用了 shell 命令,确认 Windows 上有对应的命令,或者用 Node.js 的 child_process 替代。

CC Switch、Cline MCP、Codex auth.json 相关配置。如果你同时用多个客户端,注意每个客户端的配置格式不同。CC Switch 用 JSON 配置,Cline MCP 用 MCP 协议配置,Codex 用 auth.json。三件套都是 Base URL、Key、Model ID,但字段名不同。Claude Code 用 ANTHROPIC_BASE_URL、ANTHROPIC_API_KEY、ANTHROPIC_MODEL。Cline MCP 在 MCP 配置里填 TaoToken 的 API 地址。Codex auth.json 里填 API Key 和 Base URL。确认每个客户端的字段名对应正确。

排查顺序建议:先用模型对话页面 https://taotoken.net/chat 确认 Key 可用,再检查 Claude Code 的环境变量,再检查 settings.json 格式,最后检查 Hook 脚本。这样能快速定位问题在哪一层。

6. 语义一致 CTA:把 Hooks 边界固化到工程流程里

Hooks 的价值不是让 Claude Code 更吵,而是把机械动作移出模型推理。格式化不用问 Claude,日志不用问 Claude,通知不用问 Claude,危险路径拦截也不用问 Claude。确定性规则放在 Hooks,需要理解上下文的判断留给 Claude。

在一个大型前端仓库里,如果每次让 Claude 改组件都要求它记住运行 npm run lint、npm test、npx prettier,这些指令会不断占上下文,还会和真实业务问题抢注意力。更好的结构是:CLAUDE.md 只保留核心约定,Skill 保存复杂流程,MCP 连接外部系统,Subagent 隔离大规模检索,Hooks 负责生命周期副作用。这样 Claude Code 的上下文更清爽,执行链路也更稳定。

回到 Hooks 的一句话定义:它是在触发点上运行的自动化脚本,默认不加载知识,默认不占上下文,只有把结果写回 Claude 时才开始产生上下文成本。把它用于 linting、logging、通知、权限闸门、轻量状态维护,它会成为 Claude Code 工程化里很锋利的一层。把它当成大号 prompt 注入器,它很快又会变成另一个上下文垃圾桶。

如果你还没配 TaoToken 通道,先去 https://taotoken.net/api-keys 创建一个 Key,然后按第 2 节的配置写入环境变量或 settings.json。接入文档在 https://taotoken.net/doc ,里面有各客户端的详细示例。想先验证模型是否可用,去 https://taotoken.net/chat 发一句话测试。长期编码和 Agent 场景,可以看 https://taotoken.net/coding-plan 。

真正成熟的 Claude Code 配置,不是把所有能力都打开,而是清楚知道每种能力应该待在自己的边界里。Hooks 待在生命周期边界内,TaoToken 待在通道层,Claude Code 待在代理层。三层各司其职,自动化才能稳定跑下去。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询