☰
读懂 Claude Code 架构分析系列,第一篇,开始!
2026/10/18 3:50:12 网站建设 项目流程

1. 从一次终端输入说起:Claude Code 的 Agentic CLI 到底是什么

你可能已经用过grep、curl、git这类传统命令行工具,它们的共同点是:一条命令对应一个确定动作,输入什么就输出什么,中间没有“思考”。而 Claude Code 这类 Agentic CLI 完全不是这个逻辑——你输入一句自然语言,比如“给登录函数加错误处理”,它会自己决定先读哪个文件、再改哪一行、要不要跑测试,然后根据结果继续下一步,直到任务完成或你按下 Ctrl+C。

这个“自己决定下一步”的能力,来自一个叫 Query Loop 的核心循环。它不是一个函数调用,而是一个异步生成器(async generator),源码在query.ts里大约 1700 行。整个 Claude Code 的架构可以拆成六个核心抽象:Query Loop、Tool System、Tasks、State、Memory、Hooks。其中 Query Loop 是心跳,工具是手脚,权限是刹车,状态是神经,记忆是经验,钩子是纪律。

这篇文章的目标不是让你读完就“懂了”,而是让你能真正在本地跑起来、看到事件流、验证每一层的行为。我会先带你把环境配好,用 TaoToken 作为模型接入层,然后逐层拆解 Query Loop 的调用链路,最后给出常见报错的排查方法。适合谁看?适合已经用过 Claude Code 但想搞清楚它内部怎么运转的开发者,也适合想自己搭一个 Agentic CLI 原型的工程师。

2. 前置准备:用 TaoToken 接入 Claude Code 的本地运行环境

在拆架构之前,你得先有一个能跑起来的 Claude Code 实例。Claude Code 本身是一个 TypeScript 单体应用,通过 npm 安装后以 CLI 形式运行。它默认走 Anthropic 官方 API,但国内开发者直接调用会遇到网络和计费问题。我试过用 TaoToken 作为统一接入层,它提供兼容 Anthropic 协议的 API 端点,配置简单,适合做本地实验。

TaoToken 的官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点是 https://taotoken.net/api 。你需要先在控制台创建一个 API Key,然后把它写进 Claude Code 的配置里。

Claude Code 读取配置的方式有几种:环境变量、~/.claude/settings.json、项目级.claude/settings.json。我推荐用项目级配置,这样不同项目可以隔离。先安装 Claude Code:

npm install -g @anthropic-ai/claude-code

安装完成后,验证版本:

claude --version

接下来创建项目目录并初始化配置:

mkdir claude-code-arch && cd claude-code-arch mkdir -p .claude

然后在.claude/settings.json里写入以下内容。注意 Base URL 要指向 TaoToken 的 API 地址,Key 换成你自己的:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-your-taotoken-key-here", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" } }

如果你更习惯用环境变量,也可以直接 export:

export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_API_KEY="sk-your-taotoken-key-here" export ANTHROPIC_MODEL="claude-sonnet-4-20250514"

这里有个坑要注意:Claude Code 在启动时会读取ANTHROPIC_BASE_URL,但如果你同时装了多个版本或者之前配过官方 Key,可能会被覆盖。建议用claude config list检查当前生效的配置。另外,Model ID 必须写完整,不能只写claude-sonnet,否则会报 404。

配置完成后,进入项目目录,运行:

claude

如果看到交互式 REPL 界面,说明接入成功。你可以先输入一句“你好”测试连通性。如果返回正常文本,就可以进入下一步——用无头模式观察 Query Loop 的事件流。

3. 可复制配置:用 settings.json 固定 Base URL、Key 与 Model ID

上一节我们用了环境变量和项目级 settings.json,这一节把配置固化下来,方便后续反复实验。Claude Code 的配置优先级是:命令行参数 > 环境变量 > 项目级 settings.json > 用户级 settings.json。所以如果你在项目里写了 settings.json,它会覆盖全局配置。

先看一个完整的.claude/settings.json示例,包含权限模式和工具白名单:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-your-taotoken-key-here", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" }, "permissions": { "allow": [ "Read", "Grep", "Glob" ], "deny": [ "Bash(rm -rf *)", "Bash(curl *)" ], "defaultMode": "default" } }

这里的defaultMode对应源码里的权限模式之一。Claude Code 源码层面有七种权限模式:bypassPermissions、dontAsk、auto、acceptEdits、default、plan、bubble。你在 REPL 里能切换的通常只有default、acceptEdits、plan这几种。bubble是 sub-agent 专用的,遇到危险动作会向上抛给父 Agent。

如果你想让 Claude Code 在读取文件时自动批准,但写操作仍然询问,可以设成:

{ "permissions": { "defaultMode": "acceptEdits" } }

注意,acceptEdits只自动批准文件编辑,Bash 命令仍然会问。我见过很多国内开发者直接开bypassPermissions,这在实验环境无所谓,但如果你在真实项目里跑,建议至少用acceptEdits。

配置写好后,用claude config list验证:

claude config list

你应该能看到ANTHROPIC_BASE_URL指向https://taotoken.net/api,ANTHROPIC_MODEL是你设置的 Model ID。如果没生效,检查是不是在错误的目录下运行,或者被 shell 里的 export 覆盖了。

另外,如果你用 Cline 或 CC Switch 这类工具管理多个模型端点,需要确保三件套一致:Base URL、API Key、Model ID。任何一个对不上都会导致 401 或 404。TaoToken 的 API Key 可以在控制台的 API Keys 页面创建,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。

4. 验证请求:用 claude -p 观察 Query Loop 的事件流

配置好之后,最直接的验证方式是用无头模式claude -p跑一条简单指令,然后观察它输出的 JSON 事件流。这能让你直观看到 Query Loop 是怎么一轮一轮跑的。

先跑一个最简单的:

claude -p "你好" --output-format stream-json

你会看到类似下面的事件序列(数据已脱敏):

{"type":"system","subtype":"init","cwd":"<redacted>","session_id":"<redacted>","tools":["Read","Edit","Bash"],"model":"<model>"} {"type":"system","subtype":"status","status":"requesting"} {"type":"stream_event","event":{"type":"message_start"}} {"type":"stream_event","event":{"type":"content_block_delta","delta":{"type":"text_delta","text":"你好!"}}} {"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"你好!"}]}} {"type":"stream_event","event":{"type":"message_delta","delta":{"stop_reason":"end_turn"}}} {"type":"result","subtype":"success","result":"你好!","stop_reason":"end_turn"}

逐条解释一下:

system/init表示会话初始化,里面带了当前工作目录、session ID、可用工具列表和模型名。system/status requesting表示开始请求模型。stream_event/message_start是模型开始返回流式响应。stream_event/content_block_delta是模型在持续输出文本。assistant是 SDK 整理出来的 assistant Message,方便上层消费。stream_event/message_delta是消息级别的更新,这里带了stop_reason。最后的result/success表示整个query()执行完成。

这个事件流就是 Query Loop 的输出。它是一个 async generator,外面用for await一段一段消费。伪代码大概是这样:

for await (const event of query(input)) { render(event); }

这种设计的好处是控制权在外层。外层处理完一条消息,才继续拉下一条。如果外层是终端 UI,用户按 Ctrl+C 时,可以沿着这条链路通知正在运行的任务中断,并把停止原因标记为用户取消。

现在跑一个会触发工具调用的指令,观察工具事件:

claude -p "读取当前目录下的 package.json 并告诉我 name 字段" --output-format stream-json

你会看到中间多了tool_use和tool_result类型的事件。这说明 Query Loop 在模型返回工具调用后,交给了 Tool System 执行,然后把结果追加回上下文,再继续下一轮。整个循环直到模型不再请求工具为止。

如果你想看更详细的调用链路,可以加--debug参数:

claude -p "读取 package.json" --debug

这会打印出 Query Loop 每一轮的输入消息和输出事件,适合对照源码理解。

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

配置和验证过程中最容易遇到几类报错,这里逐个拆解。

401 Unauthorized:最常见的原因是 API Key 写错或过期。先检查.claude/settings.json里的ANTHROPIC_API_KEY是否和 TaoToken 控制台里的一致。注意 Key 通常以sk-开头,不要有多余空格。如果用的是环境变量,用echo $ANTHROPIC_API_KEY确认。另外,Base URL 必须指向https://taotoken.net/api,不能带尾部斜杠,也不能写成https://taotoken.net/api/v1,否则会 404 或 401。

local proxy failed:这个报错通常出现在你本地配了 HTTP 代理,但代理没启动或端口不对。Claude Code 会读取HTTP_PROXY和HTTPS_PROXY环境变量。如果你不需要代理,直接 unset:

unset HTTP_PROXY unset HTTPS_PROXY

然后重新运行。如果你确实需要走本地代理,确保代理进程在监听,并且端口和配置一致。

reading choices:这个报错一般出现在模型返回的响应格式不符合预期时。常见原因是 Model ID 写错了,比如写成了claude-sonnet而不是完整的claude-sonnet-4-20250514。另一个原因是 Base URL 指向了一个不兼容 Anthropic 协议的端点。检查ANTHROPIC_MODEL是否和 TaoToken 支持的模型列表一致。

OAuth 相关报错:如果你之前用claude login登录过官方账号,配置里可能残留了 OAuth token。Claude Code 会优先使用 OAuth 而不是 API Key。解决办法是删除~/.claude/下的凭证文件,或者运行:

claude logout

然后重新用 API Key 配置。如果你用的是 CC Switch 或 Cline 管理多端点,确保没有同时启用 OAuth 和 API Key。

还有一个容易忽略的点:如果你在.claude/settings.json里配了permissions.deny,但工具调用被拒绝了,事件流里会出现permission_denied。这时候检查 deny 列表是不是写得太宽,比如Bash(curl *)会阻止所有 curl 命令。

排查完这些,重新跑claude -p "你好" --output-format stream-json,如果能看到完整的result/success,说明接入层已经通了。

6. 继续深入:从 Query Loop 到多 Provider 架构的下一步

到这里,你已经有了一个能跑的 Claude Code 实例,也看到了 Query Loop 的事件流。接下来可以沿着这条链路继续往下拆。

Query Loop 的核心是一个for await循环,它调用模型、接收流式响应、收集工具调用、执行工具、把结果追加回上下文,然后继续下一轮。工具系统里有一个 StreamingToolExecutor,它不会等模型整段回复结束才执行工具,只要看到并发安全的工具(比如 Read、Grep),就会提前启动。这叫 speculative execution,代价是有可能重跑,但换来了更低的延迟。

Tasks 是递归的 Query Loop。当 AgentTool fork 出一个 sub-agent 时,sub-agent 走的是同一套 query loop,只是有自己的上下文、工具集合和权限模式。sub-agent 默认走 bubble 模式,遇到危险动作不能自己批准,要向上抛给父 Agent。

State 分两层:一层是会话级的可变单例 STATE,保存 cwd、sessionId、model、permissionMode 等大约 80 个字段;另一层是 UI 状态,用 Zustand 做响应式管理。Memory 是跨会话的持久上下文,通过~/.claude/CLAUDE.md、项目级CLAUDE.md、模块级CLAUDE.md三层加载。Hooks 是生命周期拦截器,在工具调用前后、prompt 提交、会话结束等 27 个事件上触发。

多 Provider 架构通过工厂函数getAnthropicClient()在启动时决定用哪个 provider,然后适配器模式让外层用同一套接口调用。Query Loop 不关心底层是 Direct API 还是其他云厂商,职责分离得很干净。

下一篇我会从启动流程开始,拆解 Claude Code 从 CLI 入口到 REPL 初始化的完整链路。如果你想先自己动手,可以用claude -p "读取 package.json" --debug观察每一轮的消息流转,对照query.ts的源码理解。遇到问题可以查接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite ,或者直接在模型对话里问:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。如果你打算长期跑编码任务或 Agent 实验,Coding Plan 会更划算:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite 。

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

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

立即咨询