Claude Code 源码总架构分析
泄漏规模:1,884 个 TypeScript 文件 · 512,664 行代码 运行时:Bun · UI:React/Ink · API:@anthropic-ai/sdk
一句话定义
Claude Code 是一个 Tool-call 驱动的智能体循环系统。用户输入 → LLM 判断 → 调用工具 → 结果返回 LLM → 循环,直到 LLM 认为任务完成。
六大子系统关系图
┌─────────────────────────────────────────────────────────────┐
│ 01 · 入口层 main.tsx │
│ 用户在终端输入 → CLI 解析 → React/Ink 渲染器 │
│ 启动时并行预取:MDM | Keychain | API预连接 | 特性开关 │
└───────────────────────────┬─────────────────────────────────┘
│ 用户消息
▼
┌─────────────────────────────────────────────────────────────┐
│ 02 · 查询引擎 QueryEngine.ts │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ while(true) Tool-call 主循环 │ │
│ │ 压缩管道 → callModel → 工具执行 → 结果收集 → 检查 │ │
│ └─────────────────────────────────────────────────────┘ │
└────────┬──────────────┬──────────────┬──────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 03 · 工具系统│ │04 · 命令系统│ │05 · 权限系统│
│ tools/ │ │ commands/ │ │ hooks/ │
│ ~40 个工具 │ │ ~50 个命令 │ │ 4种权限模式 │
└──────┬──────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 06 · 多智能体协调 coordinator/ │
│ 子智能体生成(AgentTool) · 通信(SendMessageTool) │
│ 编排(AgentRegistry / MessageRouter / LifecycleManager) │
└─────────────────────────────────────────────────────────────┘
技术栈一览
| 层面 | 技术 | 关键点 |
|---|---|---|
| 语言 | TypeScript 5.x strict | 运行时 Zod 校验配合静态类型 |
| 运行时 | Bun(非 Node.js) | 原生执行 TS,冷启动 <50ms,内置 SQLite |
| 终端 UI | React 18 + Ink 4 | Virtual DOM diff → ANSI 控制序列,支持流式渲染 |
| CLI 解析 | Commander.js | 子命令/选项/帮助生成 |
| Schema 校验 | Zod v4 | 工具输入参数运行时校验 |
| API 客户端 | @anthropic-ai/sdk | 官方 SDK,流式 SSE |
| 代码搜索 | ripgrep | Rust 实现,比 grep 快 10x |
| 遥测 | OpenTelemetry + gRPC | 懒加载,不影响冷启动 |
| 特性开关 | GrowthBook + Bun bundle | 编译期消除,生产包中死代码物理消失 |
核心数据流
用户输入
↓
processUserInput() ← 斜杠命令拦截、@文件注入、记忆附件
↓
recordTranscript() ← 写 ~/.claude/sessions/<id>.jsonl(支持 /resume)
↓
query() 主循环
↓
[压缩检查] → [callModel SSE] → [工具并行执行] → [权限检查] → [结果收集]
↓
needsFollowUp?
是 → 追加 tool_result,继续循环
否 → 生成最终结果,Ink 渲染输出
01 · 入口层 — main.tsx
文件:
src/main.tsx(803K 行,含全部 React 组件) 职责:CLI 解析 · 并行预取 · React/Ink 渲染器初始化 · 会话启动
整体定位
入口层是用户与系统的第一接触点,做两件事:
- 解析用户意图:通过 Commander.js 将命令行参数转换为程序可理解的配置
- 搭建执行舞台:初始化 QueryEngine、注入工具/命令/权限配置,启动 React/Ink 渲染器进入交互循环
入口层本身不包含业务逻辑,是纯粹的组装层。
启动流程全景
用户执行 claude 命令
│
▼
Commander.js 解析参数
├─ claude → 交互模式(REPL)
├─ claude "prompt" → 单次执行模式
├─ claude --resume → 恢复上次会话
├─ claude --model xxx → 指定模型
└─ claude /cmd args → 直接执行斜杠命令
│
▼
并行预取阶段(关键性能优化)
│
▼
组装 QueryEngineConfig
│
▼
React/Ink 渲染器启动
│
▼
进入交互循环(用户可输入)
并行预取优化
这是入口层最重要的工程设计,将串行启动变为并行,冷启动时间从 ~400ms 压缩到 ~100ms。
启动瞬间,同时发起四个独立 I/O:
① MDM 配置读取
企业管理设备(macOS Managed Device)的策略配置
决定哪些功能被企业禁用、哪些服务器可访问
② macOS Keychain 预取
读取存储在 Keychain 中的 API Key 和 OAuth Token
避免首次请求时的 Keychain 弹窗延迟
③ Anthropic API 预连接
提前建立 TCP 连接到 api.anthropic.com
减少用户首次发送消息时的网络握手延迟
④ GrowthBook 特性开关初始化
拉取当前账户的特性开关配置
决定哪些实验性功能(VOICE_MODE、DAEMON 等)对本用户开启
React/Ink:为什么在终端用 React
Ink 是 React 的终端适配器,将组件树的 Virtual DOM diff 结果转换为 ANSI 转义序列输出到终端。
React 组件树
↓ Virtual DOM diff
Ink 渲染器
↓ 转换
ANSI 控制序列
↓ 输出
终端显示
核心价值:LLM 输出 token 时,只需 setState(prev => prev + newToken),React diff 自动计算最小变化,Ink 只更新终端中实际变化的部分,而不是重绘整个界面。
渲染层级:
<App> ← 顶层,持有 QueryEngine 引用
<ConversationHistory /> ← 历史消息列表(滚动)
<StreamingOutput /> ← 当前正在输出的内容(实时更新)
<ToolExecutionStatus /> ← 工具执行状态(并行进度条)
<PermissionPrompt /> ← 权限确认弹窗(阻塞式)
<StatusBar /> ← 底部:模型名 / token 用量 / 成本
<InputBox /> ← 用户输入框
</App>
QueryEngineConfig 组装
入口层的核心职责是组装 QueryEngine 的完整配置(依赖注入):
QueryEngineConfig = {
tools: 从 tools/ 目录加载的 ~40 个工具实例
commands: 从 commands/ 目录加载的 ~50 个命令
mcpClients: 从配置文件加载的 MCP 服务器连接
skills: 从 ~/.claude/skills/ 动态加载的技能
canUseTool: 权限检查回调(注入权限系统)
model: 当前选择的模型(可被 --model 覆盖)
maxTurns: 最大工具调用轮次(默认无限制)
maxBudgetUsd:USD 成本上限(默认无限制)
}
| 原则 | 体现 |
|---|---|
| 入口层只做组装 | 不包含业务逻辑,只负责初始化和连接各子系统 |
| 并行优先 | 所有无依赖的 I/O 操作并行发起 |
| 懒加载重模块 | OTel、gRPC 等通过动态 import() 按需加载 |
| 依赖注入 | 工具集、权限逻辑、状态管理全部注入,不硬编码 |
02 · 查询引擎 — QueryEngine.ts
文件:
src/services/QueryEngine.ts(46,630 行,系统最核心文件) 职责:LLM API 调用 · Tool-call 主循环 · 上下文压缩 · 错误恢复 · Token 计费
整体定位
QueryEngine 是整个 Claude Code 的中枢神经系统。所有其他子系统都服务于它——工具系统提供可调用的能力,权限系统提供安全约束,命令系统提供快捷操作。QueryEngine 本身只做一件事:
驱动 LLM 与工具之间的对话循环,直到任务完成。
Tool-call 主循环:一个 Turn 的六个阶段
Turn 开始
│
├─ Phase 1:上下文压缩检查
│ 检查当前消息历史是否接近 token 上限
│ 根据压力大小触发对应级别的压缩策略
│
├─ Phase 2:调用 LLM(流式)
│ 发送消息历史给 Claude API
│ 以 SSE 流的形式接收响应
│ 工具调用在流式输出期间并行启动(不等 LLM 完成)
│
├─ Phase 3:错误处理与恢复
│ 处理 prompt_too_long(触发压缩重试)
│ 处理 max_output_tokens(3级升级恢复策略)
│ 处理模型失败(降级到 fallbackModel)
│
├─ Phase 4:工具结果收集
│ 等待所有并行工具执行完成
│ 通过权限系统检查每个工具调用
│ 收集合法的工具结果
│
├─ Phase 5:附件注入
│ 从持久化记忆中读取相关记忆块
│ 动态发现并注入匹配的技能模板
│
└─ Phase 6:轮次与预算检查
turnCount++
是否超过 maxTurns?→ 停止
是否超过 maxBudgetUsd?→ 停止
needsFollowUp?→ 继续 or 退出
流式并行工具执行
Claude Code 在 LLM 仍在流式输出时,已经开始执行工具了:
传统串行(浪费等待时间):
[──── LLM 输出 50ms ────][工具A 30ms][工具B 20ms] = 100ms
Claude Code 并行(重叠执行):
[──── LLM 输出 50ms ────]
[──工具A 30ms──] (LLM 输出到一半就开始了)
[──工具B 20ms─]
总计 = max(50, 50+20) = 70ms 省了 30%
5 级上下文压缩管道
上下文使用率
100% ─── API 拒绝边界 ──────────────────────────────────
95% ─── Level 5: Autocompact ──────────────────────────
触发完整会话摘要,用摘要替换全部历史消息
85% ─── Level 4: Context Collapse ─────────────────────
折叠早期多轮对话为一个摘要块
70% ─── Level 3: Microcompact ─────────────────────────
针对重复文件编辑去重,删除中间状态
55% ─── Level 2: Snip Compact ─────────────────────────
删除最旧的若干轮消息
0% ─── Level 1: 内容替换(始终运行)────────────────────
裁剪超过阈值的单条工具结果(如巨大的 bash 输出)
| 级别 | 信息损失 | 速度 | 适用场景 |
|---|---|---|---|
| 1 · 内容替换 | 最小(仅截断单条超大输出) | 即时 | 任何时候 |
| 2 · Snip | 丢失旧历史 | 快 | 历史不重要时 |
| 3 · Microcompact | 丢失文件编辑中间状态 | 快 | 大量文件操作后 |
| 4 · Collapse | 历史细节折叠为摘要 | 中(需调用 LLM) | 早期对话不再相关 |
| 5 · Autocompact | 全历史替换为摘要 | 慢(需调用 LLM) | 必要时的最后手段 |
3 级输出恢复策略
LLM 输出被截断(stop_reason: max_tokens)时:
首次遇到截断
→ Slot 升级:将输出 token 上限从 8k 提升到 64k
→ 若仍截断,进入多轮续写(最多 3 次)
→ 3 次均失败 → 返回已完成的部分,标记错误
模型降级与孤儿清理
当主模型在流式输出中途崩溃,消息历史会出现”孤儿”tool_use。修复方式(Tombstone 模式):
原始(非法):
assistant: { tool_use: { id: 'X', name: 'BashTool' } }
(缺少对应 tool_result)
修复后(合法):
assistant: { tool_use: { id: 'X', name: 'BashTool' } }
user: { tool_result: { id: 'X', is_error: true, content: '[interrupted]' } }
→ 切换到 fallbackModel 重试
03 · 工具系统 — tools/
目录:
src/tools/(约 40 个工具) 职责:为 LLM 提供与真实世界交互的能力,每个工具是自包含的独立模块
统一工具接口
每个工具必须提供:
name 工具名称(LLM 用这个名字调用它)
description 自然语言描述(LLM 通过这段描述决定何时使用它)
inputSchema 输入参数定义(Zod Schema,运行时校验)
execute() 实际执行逻辑
requiresPermission() 是否需要用户确认
permissionDescription() 权限提示文案
工具分类详解
第一类:文件操作(4 个核心工具)
FileReadTool → 先读(了解现状)
↓
FileEditTool → 精确改(最常用,old_string → new_string)
FileWriteTool → 全量写(新建或完全重写)
BashTool → 批量操作(mv、cp、mkdir 等)
FileEditTool 的核心约束:必须先用 FileReadTool 读取文件,才能编辑。
第二类:搜索(4 个工具)
| 工具 | 功能 | 特点 |
|---|---|---|
| GlobTool | 文件路径模式匹配 | 按修改时间排序,无需权限 |
| GrepTool | 内容搜索 | 基于 ripgrep,比 grep 快 10x |
| WebSearchTool | 网络搜索 | 返回摘要 + URL 列表 |
| WebFetchTool | 网页内容抓取 | 自动转 Markdown,支持 maxLength |
第三类:智能体(2 个工具)
- AgentTool — 创建独立 Bun 子进程,运行完整 QueryEngine,执行子任务
- SendMessageTool — 向已创建的子智能体发送消息(同步/异步两种模式)
第四类:任务管理(6 个工具)
任务状态流转:pending → in_progress → completed / cancelled
TaskCreate / TaskUpdate / TaskList / TaskGet / TaskOutput / TaskStop
第五类:协议集成(2 个工具)
- MCPTool — 动态代理调用任何已连接的 MCP 服务端工具
- LSPTool — 连接本地语言服务器,提供跳转定义、查找引用等 IDE 能力
第六类:模式控制(4 个工具)
- EnterPlanModeTool / ExitPlanModeTool — 计划模式(只读自动批准,写操作只展示计划)
- EnterWorktreeTool / ExitWorktreeTool — Git Worktree 沙箱模式
第七类:笔记本(2 个工具)
- NotebookReadTool — 读取
.ipynb格式,返回代码 + 执行输出 - NotebookEditTool — 修改指定单元格,不重写整个 notebook
工具的权限声明
低权限工具(自动批准):
GlobTool, GrepTool, FileReadTool, WebSearchTool
→ 只读,不改变任何状态
中权限工具(默认模式下需确认):
FileWriteTool, FileEditTool, AgentTool
→ 有持久副作用,但可预期
高权限工具(每次必须确认):
BashTool
→ 可能执行任意命令,风险不可预测
04 · 命令系统 — commands/
目录:
src/commands/(约 50 个斜杠命令) 职责:用户通过/command触发,直接执行,不经过 LLM
命令的拦截时机
用户输入 "/compact"
↓
processUserInput() 检测到以 "/" 开头
↓
查找 commands/ 目录中匹配的命令处理器
↓
直接执行,不送入 QueryEngine 主循环
工具 vs 命令:本质区别
工具(tools/):
调用方:LLM(通过 tool_use API)
触发逻辑:LLM 根据理解决定何时调用
典型例子:BashTool, FileReadTool, GrepTool
命令(commands/):
调用方:用户(通过 /command 语法)
触发逻辑:用户显式触发
典型例子:/commit, /compact, /cost
工具是 Claude 的双手,Claude 决定何时伸手;命令是遥控器上的按钮,用户决定何时按下。
命令分类详解
第一类:Git 工作流命令
| 命令 | 功能 |
|---|---|
/commit | 读取 git diff --staged → LLM 生成 commit message → 用户确认 → 执行 |
/commit-push-pr | 一键 commit + push + 创建 PR(PR 描述由 LLM 自动生成) |
/pr | 仅创建 PR,分析 git diff main...HEAD 生成标准描述 |
第二类:代码质量命令
/review— AI 审查,输出结构化报告(问题点、严重程度、建议)/ultrareview— 多维度深度审查(安全性、性能、可维护性、逻辑正确性)/autofix-pr— 自动识别问题 → 生成修复 → 创建 PR
第三类:上下文管理命令
/context → 查看现状(消息数、Token 用量、压缩历史)
/compact → 历史太多,但想保留语义(触发 Level 5 Autocompact)
/clear → 完全换话题,彻底丢弃历史
第四类:配置与集成命令
/config— 运行时配置(切换模型、修改 maxTurns、开关 thinking 模式)/mcp— 管理 MCP 服务器连接(list/add/remove/restart)/memory— 查看和管理持久化记忆/permissions— 查看当前权限配置和白名单
第五类:会话管理命令
/resume— 恢复历史会话(读取.jsonl文件重建消息历史)/cost— 显示完整成本分析(含子智能体成本)/status— 系统状态快照(Turn 数、活跃子智能体、任务队列)
第六类:技能命令(动态扩展)
~/.claude/skills/
├── review-security.md → 触发 /review-security
├── generate-tests.md → 触发 /generate-tests
└── refactor-to-ts.md → 触发 /refactor-to-ts
技能文件触发后,模板内容被注入为系统附件,引导 LLM 行为(而非直接执行逻辑)。
05 · 权限系统 — hooks/toolPermission/
目录:
src/hooks/toolPermission/职责:在每次工具执行前进行安全检查,决定是否允许执行
四种权限模式
| 模式 | 适用场景 | 行为 | 自动化程度 |
|---|---|---|---|
default | 日常开发 | 危险操作弹出确认提示 | 最低 |
plan | 只读分析 | 读操作自动批准,写操作展示计划不执行 | 中 |
auto | 批量处理 | 分类器评估风险,低风险自动批准 | 高 |
bypassPermissions | CI/CD 流水线 | 跳过所有权限检查 | 最高 |
wrappedCanUseTool:完整决策流程
工具调用请求到达
│
[检查 1] bypassPermissions 模式? 是 → 直接批准
│
[检查 2] 命中永久批准白名单? 是 → 直接批准
│
[检查 3] plan 模式?
是,只读工具 → 批准
是,写入工具 → 展示计划,不执行
│
[检查 4] auto 模式?
是 → 分类器评估:低风险 → 批准,高风险 → 降级为 default
│
[检查 5] default 模式:显示交互式确认
批准(本次)→ 执行
批准(始终)→ 执行 + 加入永久白名单
拒绝 → 记录到 permissionDenials[]
永久批准白名单
存储:~/.claude/approvals.json
结构:Map<toolName, Set<serializedInput>>
示例:
{
"BashTool": ["npm test", "npm run build", "git status"],
"FileWriteTool": ["/project/src/utils.ts"],
}
白名单是精确匹配的,npm test 不会覆盖 npm test --watch。
auto 模式的风险分类器
评估维度:
工具类型
只读工具(Glob, Grep, FileRead) → 低风险
写入工具(FileWrite, FileEdit) → 中等风险
执行工具(BashTool) → 高风险(默认)
参数内容分析(仅 BashTool)
rm -rf ... → 极高风险
sudo ... → 高风险
curl | bash → 极高风险
git status → 低风险
npm test → 低风险
目录范围:超出 allowedDirectories → 风险升级一级
权限与 QueryEngine 的解耦
权限逻辑通过回调注入,QueryEngine 不知道实现细节:
// QueryEngineConfig 中:
canUseTool: (tool, input) => Promise<boolean>
// 这意味着:
// 测试时注入永远返回 true 的假权限函数
// 不同环境(CLI、IDE、CI)注入不同的权限逻辑
06 · 多智能体协调 — coordinator/
目录:
src/coordinator/·src/tools/AgentTool/·src/tools/SendMessageTool/职责:子智能体生成、智能体间通信、生命周期编排
架构全景
主智能体(Root Agent)
QueryEngine 主循环
mutableMessages(主上下文)
totalUsage(汇总全树成本)
│
│ 调用 AgentTool
▼
┌──────────────── coordinator ─────────────────┐
│ AgentRegistry 注册表,id → 进程句柄 │
│ LifecycleManager 进程创建/监控/回收 │
│ MessageRouter SendMessageTool 消息路由 │
│ SharedStateProxy 只读状态共享 │
└──────┬───────────────────────┬───────────────┘
│ │
▼ ▼
子智能体 A(Bun 进程) 子智能体 B(Bun 进程)
独立 QueryEngine 独立 QueryEngine
独立消息历史 独立消息历史
受限工具集 受限工具集
为什么选择进程而非线程
进程方案(Claude Code 采用)的优势:
每个子智能体 = 独立 Bun 子进程
→ 内存天然隔离,无需加锁
→ 子进程崩溃不影响主进程
→ OS 可对进程设置 CPU/内存上限
→ 可以真正并行利用多核 CPU
工具集的继承与限制
子智能体的工具集由父级显式注入,遵循最小权限原则:
父级创建子智能体时指定:
tools: ['GlobTool', 'GrepTool', 'FileReadTool']
子智能体:
GlobTool ✓ GrepTool ✓ FileReadTool ✓
BashTool ✗ FileWriteTool ✗ AgentTool ✗
嵌套深度限制:最多 3 级嵌套。第 3 级时强制移除 AgentTool,防止无限递归。
智能体间通信:SendMessageTool
所有通信都经过 coordinator,智能体之间不直接引用对方:
智能体 A 调用 SendMessageTool(to='agent_B_id', message='...')
→ coordinator.MessageRouter 接收
→ 查询 AgentRegistry,找到 agent_B 的进程句柄
→ 将消息写入 agent_B 的 stdin
→ agent_B 的 LLM 处理,响应流回 coordinator
→ coordinator 将响应作为 tool_result 返回给智能体 A
特殊路由目标:
to='__parent__'— 发送给父智能体to='*'— 广播给所有同级智能体to='agent_xxx_id'— 精确点对点
三种执行模式
串行分解模式:
主智能体 → 子A(扫描结构)→ 子B(分析依赖)→ 子C(生成报告)
并行扇出模式:
主智能体 → 子A(审查模块1) ┐
→ 子B(审查模块2) ├→ 汇总
→ 子C(审查模块3) ┘
总时间 ≈ 最慢的那个,而非 3 个之和
层级委托模式:
主智能体:"重构整个代码库"
→ 子A:"重构 frontend"
→ 子-子A1:"重构组件"
→ 子-子A2:"重构样式"
→ 子B:"重构 backend"
Token 与成本的全树追踪
/cost 显示的是"全树"成本:
总成本:$0.127
主智能体自身: $0.031
子智能体 A(含子树): $0.063
子智能体 B: $0.033
故障隔离与容错
子智能体崩溃时:
LifecycleManager 检测到异常退出
→ AgentTool 收到失败结果
→ 向主 QueryEngine 返回 error tool_result
→ 主 LLM 决策:重试 / 降级 / 向用户说明
→ 主智能体继续运行(完全不受影响)
心跳检测:每 10 秒一次,5 秒无响应判定死亡进程,强制清理,防止僵尸进程。