深度解析 Claude Code 源码(一):整体架构与核心数据流
用户输入 `claude "修个 bug"`│▼[模块加载] MDM/Keychain 并行预热│▼[main()] 安全设置 + 特殊模式分流│▼[run()] Commander 解析命令行参数│▼[preAction] init() -> 配置/网络/安全/API预连接│▼[setup()] 工作目录/Session/权限 + 并行加载 commands/agents│▼[分流] ── -p
导语:本文是对 Claude Code (Anthropic 官方 CLI 工具) 的源码还原与深度分析。该项目使用 TypeScript + React + Ink 构建,共计 1,884 个 TS/TSX 文件,运行在 Bun 运行时之上。本文将从整体架构、启动流程、运行模式以及核心数据流四个维度,带你硬核拆解这款强大的 AI CLI 工具。
参考仓库(源码地址):https://github.com/ChinaSiro/claude-code-sourcemap
第一章 整体架构
1.1 目录结构
项目的核心逻辑集中在 src/ 目录下,各模块职责划分非常清晰:
src/
├── main.tsx # 主入口 (~800KB),CLI 解析 + 启动流程
├── entrypoints/ # 多种启动模式 (CLI/SDK/Sandbox)
├── state/ # Zustand 风格全局状态管理 (AppStateStore)
├── tools/ # 43 个工具实现 (Bash, FileEdit, Grep, Agent 等)
├── commands/ # 101 个 CLI 命令 (commit, review, config 等)
├── components/ # 144 个 React 终端 UI 组件
├── ink/ # 定制增强版 Ink 终端 UI 框架
├── services/ # 36 个服务模块 (MCP, OAuth, API, LSP 等)
├── hooks/ # React hooks + AI 行为钩子系统
├── tasks/ # 后台任务管理 (Shell/Agent/Teammate/Workflow 等)
├── utils/ # 329 个工具函数/模块
├── bridge/ # 桌面端集成 (Desktop/远程控制)
├── skills/ # AI 技能系统
├── vim/ # Vim 模式支持
├── voice/ # 语音模式
└── coordinator/ # 多 Agent 编排
1.2 核心设计
| 设计方面 | 具体实现 |
|---|---|
| 运行时 | 基于 Bun,利用 feature() 做编译期死代码消除。 |
| UI 框架 | React + 深度定制 Ink,支持焦点管理、动画、点击与滚动。 |
| 状态管理 | 不可变 AppState (DeepImmutable),Zustand 风格 store。 |
| 工具系统 | 统一 Tool 接口,内置/MCP/LSP 工具共用同一抽象层。 |
| 权限控制 | 完整的权限检查流程,支持适配符规则和逐工具控制。 |
| 功能门控 | 大量 Feature Flag (如 KAIROS, COORDINATOR_MODE, VOICE_MODE 等)。 |
| 渲染优化 | 源码中可见 React Compiler 编译器优化标记,大幅提升终端渲染性能。 |
1.3 关键文件速览
main.tsx- 主入口,负责启动编排。Tool.ts- 工具基础接口定义。tools.ts- 工具注册表。query.ts- 查询引擎(整个系统的心脏),API 调用核心,约 1700 行。QueryEngine.ts- 查询执行引擎 (SDK/headless 封装)。commands.ts- 命令注册。context.ts- 系统与用户上下文构建。
第二章 启动流程
整个启动过程像流水线一样依次执行,主要分为 5 个阶段:
2.1 第一阶段:模块加载期的并行预热 (main.tsx:1-20)
profileCheckpoint('main_tsx_entry')- 记录时间戳startMdmRawRead()- 并行读取 MDM 企业配置 (plutil/reg)startKeychainPrefetch()- 并行读取 macOS 钥匙串 (OAuth token)
💡 设计思路:利用 JS
import语句求值的 ~135ms,提前启动耗时的子进程。这些进程在后台跑,等后面需要结果时再await,这是一种非常巧妙的启动性能优化手段。
2.2 第二阶段:main() 函数 (main.tsx:585-854)
主要做两件事:
- 安全设置:防止 Windows PATH 劫持、注册 SIGINT/exit 处理。
- 特殊启动模式分流:根据
argv判断是否走特殊路径:cc:// URL- Direct Connect 模式--handle-uri- Deep Link 协议处理claude assistant- Kairos 助手模式claude ssh- SSH 远程模式- 如果都不是,则进入标准的
run()流程。
2.3 第三阶段:run() - Commander CLI 解析 (main.tsx:884-1006)
Commander 初始化并注册 preAction hook (每个命令执行前运行):
awaitMDM + Keychain 预取完成await init()- 核心初始化- 加载 analytics sinks
- 运行数据迁移 (migrations)
- 加载远程设置 + 策略限制 (非阻塞)
其中 init() 在 entrypoints/init.ts 中,完成了配置系统启用、安全环境变量应用、优雅退出清理、mTLS 配置、网络代理配置,以及提前建立 Anthropic API 的 TCP+TLS 连接 (又一个并行优化)。
2.4 第四阶段:setup() + 并行加载 (main.tsx:1903-2030)
并行执行以下三项任务:
setup()- 初始化工作目录、worktree、Session、权限模式。getCommands()- 加载所有 CLI 命令。getAgentDefinitions()- 加载 agent 定义。
2.5 第五阶段:分流到 REPL 或 Print 模式
根据是否有 -p/--print 参数进入不同运行模式(详见第三章)。
2.6 启动流程总结图
用户输入 `claude "修个 bug"`
│
▼
[模块加载] MDM/Keychain 并行预热
│
▼
[main()] 安全设置 + 特殊模式分流
│
▼
[run()] Commander 解析命令行参数
│
▼
[preAction] init() -> 配置/网络/安全/API预连接
│
▼
[setup()] 工作目录/Session/权限 + 并行加载 commands/agents
│
▼
[分流] ── -p ──> Print 模式 (QueryEngine 单次执行)
│
└── 交互模式 ──> launchRepl() -> <App><REPL /></App>
第三章 两种运行模式:REPL vs Print
REPL = Read-Eval-Print Loop (术语源自 Lisp):读取 -> 执行 -> 输出 -> 循环。
3.1 模式对比
| 特性 | REPL 模式 (交互式) | Print 模式 (非交互/headless) |
|---|---|---|
| 启动方式 | claude |
claude -p "修个bug" |
| 交互方式 | 持续对话,等待用户输入 | 执行一次就退出 |
| UI 渲染 | Ink 渲染终端 UI (React 组件树) | 无 UI,直接输出文本/JSON |
| 状态管理 | React AppState (Zustand store) | QueryEngine 类内部 |
| 核心入口 | launchRepl() -> <App><REPL /></App> |
QueryEngine.submitMessage() |
3.2 代码中的分流点
在 main.tsx 中,核心判断逻辑如下:
if (isNonInteractiveSession) {
// Print 模式:用 QueryEngine 单次执行
QueryEngine.submitMessage(prompt)
} else {
// REPL 模式:启动 Ink 渲染循环
await launchRepl(root, appProps, sessionConfig, renderAndRun)
}
replLauncher.tsx 非常简洁(仅 22 行),其核心就是挂载 React 组件:
<App {...appProps}>
<REPL {...replProps} />
</App>
第四章 核心数据流
整条数据流的核心是一个 AsyncGenerator 循环。用户每发一条消息,就触发一次 query() 调用,它通过 yield 不断向外吐出事件(流式消息、工具结果等),调用方消费这些事件来更新 UI。
4.1 三个核心文件的职责
| 文件 | 职责说明 | 形象比喻 |
|---|---|---|
context.ts |
构建上下文 (git status, CLAUDE.md, 日期) | 食材准备 |
query.ts |
单次对话循环:API调用 -> 工具执行 -> 继续/停止 | 厨师做菜 |
QueryEngine.ts |
会话级状态管理,封装 query() 给 SDK/headless 用 |
餐厅前台 |
4.2 上下文构建 (context.ts)
每次对话开始前,系统构建两种上下文(均使用 memoize 缓存,整个会话只算一次):
getSystemContext()-> 包含 git 状态 (分支、最近提交、status)getUserContext()-> 包含CLAUDE.md文件内容 + 当前日期
4.3 查询循环 (query.ts) - 系统的心脏
query() 是一个 AsyncGenerator,核心结构是一个 while(true) 循环。下面用伪代码展示其核心分支逻辑:
while (true) {
// ① 消息预处理 (裁剪大结果、历史裁剪、微压缩、上下文折叠、自动压缩)
// ② 调用 Claude API,流式收集回复
for await (msg of callModel(...)) {
yield msg // 流式吐出给 UI
if (msg 包含 tool_use) needsFollowUp = true
}
// ③ 关键分支点
if (!needsFollowUp) {
// 模型没有请求工具 -> 任务完成
return { reason: 'completed' } // <- 唯一的正常退出
}
// ④ 模型请求了工具 -> 执行工具
for await (update of runTools(...)) {
yield update.message // 工具结果给 UI
}
// ⑤ 拼接消息,准备下一轮
state = {
messages: [...之前的消息, ...assistant回复, ...工具结果],
turnCount: nextTurnCount,
transition: { reason: 'next_turn' },
}
// 没有 break/return -> 自动 continue 回到 while(true) 顶部
}
循环退出条件:
- 只有
return才会跳出循环。只要模型还在调工具,循环就不会停。 - 一个典型的 Agent 任务可能循环几十次:
读文件 -> 思考 -> 改文件 -> 运行测试 -> 再改 -> ... -> 最终给出文本回复 -> 退出。
退出状态枚举:
completed: 正常退出,模型不再调用工具。aborted_streaming: 用户按了 Ctrl+C。max_turns: 达到--max-turns限制。prompt_too_long: 上下文太长且压缩失败。hook_stopped: Hook 阻止了继续。
4.4 工具执行 (toolOrchestration.ts)
工具不是逐个执行的,而是通过 partitionToolCalls 分批并行或串行:
- 批次1:
[GrepTool, GlobTool]->isConcurrencySafe=true-> 并行执行 - 批次2:
[FileEditTool]->isConcurrencySafe=false-> 串行执行 - 批次3:
[GrepTool]->isConcurrencySafe=true-> 并行执行
注:只读工具 (Grep, Read 等) 可以并行,写操作 (Edit, Bash 等) 必须串行。默认最大并发数为 10。
4.5 QueryEngine (QueryEngine.ts)
QueryEngine 是对 query() 的类封装,管理跨轮次的状态:
class QueryEngine {
mutableMessages: Message[] // 整个会话的消息历史
totalUsage: Usage // 累计 token 用量
async *submitMessage(prompt) {
// 1. 构建 systemPrompt, userContext, systemContext
// 2. 创建 toolUseContext (权限、工具列表等)
// 3. 调用 query(params) 并 yield 结果
// 4. 累积消息到 mutableMessages
}
}
(注:REPL 模式不走 QueryEngine,而是直接调用 query(),状态由 React AppState 管理)
4.6 完整数据流图
用户输入 "帮我修 src/app.ts 的 bug"
│
▼
┌─ context.ts ──────────────────────────────────┐
│ systemContext = { gitStatus } │
│ userContext = { claudeMd, date } │
└───────────────────────────────────────────────┘
│
▼
┌─ query.ts ── while(true) 循环 ────────────────┐
│ │
│ [预处理] 压缩/裁剪消息 -> 确保不超 context window │
│ │ │
│ ▼ │
│ [API 调用] callModel(messages, systemPrompt, tools)
│ │ - 流式返回 │
│ ▼ │
│ [模型回复] "我来看看代码" + tool_use: FileReadTool │
│ │ │
│ ▼ │
│ [工具执行] runTools() -> 读取 src/app.ts -> 返回文件内容│
│ │ │
│ ▼ │
│ [拼接消息] messages += [assistant回复, tool_result]│
│ │ │
│ ▼ - continue, 回到循环顶部 │
│ │ │
│ [API 调用] callModel(更长的 messages) │
│ │ │
│ ▼ │
│ [模型回复] "找到 bug 了" + tool_use: FileEditTool │
│ │ │
│ ▼ │
│ [工具执行] runTools() -> 编辑文件 -> 返回 diff │
│ │ │
│ ▼ - continue │
│ │ │
│ [API 调用] callModel(...) │
│ │ │
│ ▼ │
│ [模型回复] "已修复!" (无 tool_use) │
│ │ │
│ ▼ │
│ [stop hooks] -> return { reason: 'completed' }│
└───────────────────────────────────────────────┘
│
▼
UI 渲染每个 yield 出来的消息
结语:Claude Code 的源码展示了极高的工程素养,无论是从启动时的性能压榨,还是对大模型上下文的精准控制,都非常值得前端和 Node.js 开发者学习。如果你觉得这篇源码解析对你有帮助,欢迎点赞、收藏、评论!
后面我将继续分享Claude code源码中《上下文管理》和《SKILL调用》的内容,感兴趣的同学们可以继续关注!
更多推荐


所有评论(0)