1. 从一次「工具调用卡住」说起:Claude Code 的架构到底长什么样
Claude Code 是 Anthropic 官方推出的 AI 编程助手 CLI 工具,基于 TypeScript + React (Ink) 构建,能在终端里读写文件、执行命令、跑测试、提交代码。它适合谁?适合想把 AI 编程助手真正接进日常开发流的人——不只是聊天,而是让它动手改你的仓库。但很多人第一次读它的源码或者配它的接入层时,会卡在同一个地方:工具调用链路太长,配置层又分散在settings.json、config.toml、环境变量三处,改完不知道哪一层生效。
我试过把它的启动流程从头跟一遍,发现真正决定「能不能跑起来」的不是模型多强,而是三件事:入口怎么初始化、工具怎么被调度、API 通道怎么被配置层解析。前两件是架构问题,第三件是接入问题。这篇就按这个顺序拆:先看 Claude Code 的入口与核心循环,再看工具系统和权限层,最后落到一份可复制的 TaoToken 配置骨架,并用一次最小请求验证连通性。读完你应该能自己判断:某个报错是出在架构层、工具层,还是配置层。
Claude Code 的源码规模很能说明问题:main.tsx单文件 803KB,query.ts68KB,QueryEngine.ts46KB,utils/目录下 9542 个文件,commands/1774 个,tools/1412 个。这不是一个「命令行聊天壳」,而是一套完整的编程代理系统。理解它的分层,比记住某个 API 名字有用得多。
2. 入口与启动:main.tsx 里藏着的性能设计
2.1 启动流程的七个阶段
Claude Code 的入口是src/main.tsx。它的启动不是线性加载,而是刻意做了并行化。整体流程大致是:性能追踪打点 → 并行预取(MDM 配置、密钥链、凭证)→ 加载 135+ 模块依赖 → Commander 解析 CLI 参数 → 初始化核心服务(特性开关、遥测、策略限制、MCP)→ 认证与授权 → 按模式分支(交互 REPL / 非交互 print / SDK QueryEngine)。
关键在第二步。它在模块还在加载的时候,就并行启动了 I/O 操作:
// 在模块加载的同时并行执行 I/O 操作 startMdmRawRead(); // 启动 MDM 子进程读取配置 startKeychainPrefetch(); // 预取系统密钥链 prefetchAwsCredentialsAndBedRockInfoIfSafe();这种「加载与 I/O 重叠」的写法,把启动延迟压了下来。另一个细节是延迟加载配合特性开关做死代码消除:
const SleepTool = feature('PROACTIVE') || feature('KAIROS') ? require('./tools/SleepTool/SleepTool.js').SleepTool : null;未启用的工具在打包阶段就被摇掉了,运行时不会为它付出解析成本。你在自己写 CLI 工具时可以借鉴这两点:把不依赖模块解析的 I/O 提前并行,把可选功能用开关包起来。
2.2 三种运行模式的分支
启动的最后一步是模式分支,这决定了后面走哪条代码路径:
| 模式 | 入口 | 适用场景 |
|---|---|---|
| 交互模式 | launchRepl() | 终端里连续对话、边聊边改 |
| 非交互模式 | cli/print.ts(212KB) | 脚本化、CI 里一次性输出 |
| SDK 模式 | QueryEngine | 被其他程序调用 |
理解这个分支很重要:你在终端里遇到的交互问题和在脚本里遇到的问题,根因往往不在同一层。交互模式的问题多半在 Ink 渲染和状态订阅,非交互模式的问题多半在输出序列化和退出码。
3. 核心循环:QueryEngine 与 query.ts 如何驱动工具调用
3.1 QueryEngine 是「大脑」,query.ts 是「心跳」
QueryEngine.ts(46KB)负责会话状态:消息历史、文件缓存、用量统计、工具执行与权限检查、多轮对话和上下文压缩。它的配置接口把依赖都显式注入:
export type QueryEngineConfig = { cwd: string tools: Tools commands: Command[] mcpClients: MCPServerConnection[] agents: AgentDefinition[] canUseTool: CanUseToolFn getAppState: () => AppState setAppState: (f) => void initialMessages?: Message[] readFileCache: FileStateCache userSpecifiedModel?: string thinkingConfig?: ThinkingConfig maxTurns?: number maxBudgetUsd?: number }注意canUseTool和maxBudgetUsd这两个字段——权限和预算是从构造时就注入的,不是运行时临时判断。这意味着接入层如果配错了模型或通道,会在很早的阶段就暴露。
真正的循环在query.ts(68KB)。它是一个while(true)的生成器,每轮做六件事:检查终止条件 → 调用 API → 流式处理响应 → 提取工具调用 → 执行工具 → 检查是否需要压缩上下文。
async function* queryLoop(params, consumedCommandUuids) { let state: State = { messages: params.messages, toolUseContext: params.toolUseContext, maxOutputTokensRecoveryCount: 0, hasAttemptedReactiveCompact: false, turnCount: 0, } while (true) { if (shouldTerminate(state)) return { terminal: true } const response = yield* callAPI(state) for await (const event of response) { yield event if (event.type === 'assistant') { state.messages.push(event) const toolUses = extractToolUses(event) if (toolUses.length > 0) { const results = yield* executeTools(toolUses, state) state.messages.push(...results) } } } if (shouldCompact(state)) { state = yield* compactContext(state) } state.turnCount++ } }3.2 工具执行与权限检查的顺序
工具调用不是「模型说执行就执行」。完整链路是:Claude 返回tool_use块 → 按名字查找工具实现 → 输入校验(Zod schema)→ 权限检查 → 执行 → 处理结果并写回消息。
权限检查本身也是分层的,顺序不能乱:
// 1. bypass 模式直接放行 // 2. 匹配 alwaysAllow 规则 → 允许 // 3. 匹配 alwaysDeny 规则 → 拒绝 // 4. auto 模式下的安全工具 → 允许 // 5. 以上都不命中 → 弹出权限对话框这个顺序解释了为什么有时候你明明在配置里写了 allow,工具还是弹窗问你——因为 deny 规则的优先级在 allow 之后、对话框之前,或者规则来源(global / project / session)没对上。
3.3 并发策略与安全校验
工具执行有并发优化,但不是无脑并发:读写操作互斥,多个读操作可以并行,命令执行默认串行。安全侧还有危险命令检测和路径校验:
function isDangerousCommand(command: string): boolean { const dangerousPatterns = [ /rm\s+-rf/i, /mkfs\./i, /dd\s+if=/i, />\s*\/dev\/[sh]d/i, /chmod\s+-R\s+777/i, /curl.*\|\s*sh/i, ] return dangerousPatterns.some(p => p.test(command)) }路径校验会拦掉..越界、工作目录外路径和符号链接。这些设计对自建代理很有参考价值:安全不是加一个确认弹窗,而是把规则前置到执行之前。
4. 配置层:settings.json 与 config.toml 的可复制骨架
4.1 为什么配置层最容易出错
Claude Code 的配置来源有三处:全局配置、项目配置、会话配置,加上环境变量。接入第三方 API 通道时,最容易出问题的是「模型名、base URL、鉴权头」这三项分散在不同文件里,改了一处没改另一处。
TaoToken 提供统一的 Key 和 API 通道,把模型对话、编码计划、控制台、API Keys 都收敛到一个入口。它的 API 地址是https://taotoken.net/api,官网是https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。下面给出两份骨架,分别对应settings.json和config.toml两种常见配置形态。
4.2 settings.json 骨架
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "sk-你的TaoToken密钥", "ANTHROPIC_MODEL": "claude-sonnet-4-5", "ANTHROPIC_SMALL_FAST_MODEL": "claude-haiku-4-5" }, "permissions": { "allow": [ "Read", "Glob", "Grep" ], "deny": [ "Bash(rm -rf:*)", "Bash(curl * | sh)" ], "ask": [ "Write", "Edit" ] }, "includeCoAuthoredBy": false }几个要点:ANTHROPIC_BASE_URL指向 TaoToken 的 API 地址,末尾不要多加斜杠;ANTHROPIC_AUTH_TOKEN用你在控制台生成的 Key;ANTHROPIC_MODEL和ANTHROPIC_SMALL_FAST_MODEL分别对应主模型和轻量任务模型。权限块里把只读工具放 allow,写操作放 ask,危险命令放 deny,这样日常读代码不会被打断,改文件时才会确认。
4.3 config.toml 骨架
有些团队用 TOML 管理配置,等价写法如下:
[env] ANTHROPIC_BASE_URL = "https://taotoken.net/api" ANTHROPIC_AUTH_TOKEN = "sk-你的TaoToken密钥" ANTHROPIC_MODEL = "claude-sonnet-4-5" ANTHROPIC_SMALL_FAST_MODEL = "claude-haiku-4-5" [permissions] allow = ["Read", "Glob", "Grep"] deny = ["Bash(rm -rf:*)", "Bash(curl * | sh)"] ask = ["Write", "Edit"]注意:密钥不要提交进 Git。把
settings.json里的 token 换成从环境变量读取,或者用.gitignore排除本地配置文件。团队共享时只提交不含密钥的模板。
4.4 环境变量方式的兜底
如果不想改配置文件,也可以直接用环境变量,适合临时验证:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_AUTH_TOKEN="sk-你的TaoToken密钥" export ANTHROPIC_MODEL="claude-sonnet-4-5"环境变量的优先级通常高于配置文件,所以排查「配置没生效」时,先env | grep ANTHROPIC看一眼有没有残留的旧值。
5. 验证连通性:一次最小请求
5.1 用 curl 直接打通道
配置写完先别急着开 Claude Code,用一条最小请求确认通道本身是通的:
curl -sS https://taotoken.net/api/v1/messages \ -H "content-type: application/json" \ -H "x-api-key: $ANTHROPIC_AUTH_TOKEN" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "claude-sonnet-4-5", "max_tokens": 64, "messages": [ {"role": "user", "content": "只回复两个字:连通"} ] }'预期返回是一段 JSON,content数组里能看到模型回复的文本。如果返回 401,说明 Key 不对或没带上;返回 404,多半是 base URL 拼错了;返回 429,是触发了速率限制,等一会儿再试。
5.2 在 Claude Code 里跑一次真实工具调用
通道通了之后,进项目目录启动 Claude Code,输入一个会触发工具调用的请求,比如「读一下 package.json 告诉我项目名」。观察两件事:一是它是否成功调用了 Read 工具,二是权限层有没有按你配置的 allow 规则放行。如果它弹窗问你是否允许读取,说明 allow 规则没匹配上,检查工具名大小写和规则来源。
5.3 用模型对话页做交叉验证
如果 CLI 里行为异常,可以去 TaoToken 的模型对话页发同样的请求,排除是通道问题还是客户端问题。模型对话入口:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite。如果对话页正常、CLI 异常,问题就在配置层或客户端版本;如果两边都异常,问题在 Key 或通道。
6. 本篇常见错排查
6.1 报错:401 Unauthorized
最常见。先确认ANTHROPIC_AUTH_TOKEN有没有真的被读到。Claude Code 读的是环境变量和配置文件,如果你在 shell 里 export 了但用的是另一个终端窗口,就不会生效。用echo $ANTHROPIC_AUTH_TOKEN确认。另外注意有些配置用ANTHROPIC_API_KEY而不是ANTHROPIC_AUTH_TOKEN,两者不要混用,按你所用版本的文档来。
6.2 报错:模型不存在 / model not found
模型名写错了,或者你的 Key 没有开通该模型的权限。把ANTHROPIC_MODEL换成控制台里列出的可用模型名。轻量任务模型ANTHROPIC_SMALL_FAST_MODEL也要单独确认,它和主模型是两套权限。
6.3 工具调用一直弹权限框
检查permissions.allow里的工具名。Claude Code 的工具名是Read、Write、Edit、Bash、Glob、Grep这种首字母大写的形式,写成小写不匹配。另外 deny 规则优先级高于 allow,如果 deny 里有一条宽泛的Bash(*),那所有命令都会被拦。
6.4 启动慢或卡在初始化
如果卡在启动阶段,先看是不是网络请求超时。Claude Code 启动时会做认证和特性开关拉取,通道不通会一直等。用第 5 节的 curl 先确认通道,再启动客户端。另外检查有没有多个配置文件冲突,项目级配置会覆盖全局配置。
6.5 上下文被压缩后「失忆」
这是query.ts里的自动压缩在起作用:当 token 数超过上下文窗口的 80% 时会触发压缩,保留最近消息和关键工具结果。如果你发现它忘了前面的约定,可以在项目根目录放一个CLAUDE.md写清长期约束,这类内容会在压缩时被优先保留。
7. 接入与后续:把配置沉淀成团队资产
把上面这套跑通之后,建议做两件事。第一,把settings.json模板化,密钥走环境变量,模板提交进仓库,新人 clone 下来填个 Key 就能用。第二,把权限规则按项目类型分档:纯读代码的项目 allow 放宽,涉及部署脚本的项目 deny 收紧。
如果你主要做长期编码和 Agent 任务,可以了解 Coding Plan,它更适合持续性的编码场景:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite。需要管理多个 Key 或查看用量,去控制台:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite。生成和管理 Key 在 API Keys 页面:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite。完整的接入参数和字段说明看接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite。如果你用的是 Claude Code 的 Anthropic 兼容模式,这份说明也值得对照:https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claude_code&utm_campaign=rewrite。
最后留一个我踩过的坑:改完配置后一定要重启 Claude Code 进程,它不会热加载settings.json。很多人以为配置没生效,其实只是旧进程还在用旧值。