乐于分享
好东西不私藏

Claude Code 源码精读 第 04 课:入口与启动流程

Claude Code 源码精读 第 04 课:入口与启动流程

模块二:启动与状态 | 前置依赖:第 01-03 课 | 预计学习时间:60 分钟


学习目标

完成本课后,你将能够:

  1. 1. 画出从 cli.tsx 到 REPL 就绪的完整启动时序图
  2. 2. 说明三个入口点(CLI/MCP/SDK)的差异
  3. 3. 列出 init.ts 的 17 步初始化序列及其目的
  4. 4. 理解迁移系统如何管理配置演进

4.1 三个入口点

Claude Code 不是只有一种启动方式。根据使用场景,有三个不同的入口:

entrypoints/├── cli.tsx          ← 终端交互模式(最常用)├── mcp.ts           ← 作为 MCP Server 运行└── sdk/             ← 作为 Agent SDK 被其他程序调用    ├── coreSchemas.ts    (55KB,核心 Schema 定义)    ├── controlSchemas.ts (控制 Schema)    └── coreTypes.ts      (类型定义)
入口
使用场景
启动后的形态
cli.tsxclaude
 命令运行
交互式 REPL,接受用户输入
mcp.ts
作为 MCP 服务器
stdio 模式,响应 MCP 客户端请求
sdk/
被其他应用调用
函数库,提供 API 接口

4.2 CLI 入口:cli.tsx 的快速路径

cli.tsx 是绝大多数用户的入口。它的第一个设计原则是:尽快响应简单请求

快速路径(无需加载完整应用)

claude --version     → 直接输出版本号,退出claude --daemon-worker → 直接启动 daemon workerclaude daemon [cmd]  → 直接管理 daemonclaude ps/logs/kill  → 直接管理会话

这些命令不需要初始化 React、加载工具、连接 MCP — 它们在加载完整模块之前就返回了。

并行预取(启动优化)

对于需要完整启动的命令,cli.tsx 在加载模块的同时启动了三个并行预取:

// cli.tsx / main.tsx 最顶部(模块加载的副作用)startMdmRawRead()       // 1. 预读 MDM(移动设备管理)配置startKeychainPrefetch() // 2. 预取 OAuth Token + Legacy API Key// 3. 正常的模块导入同时进行
时间线 ──────────────────────────────────────────►├── MDM 读取 ─────────────────┤├── Keychain 预取 ────────────┤├── 模块导入 ─────────────────────────┤                                       ├── init() 开始

三个操作并行执行,总耗时取最慢的那个,而不是三者之和。

入口分流

cli.tsx  │  ├── --version → 立即退出  ├── --dump-system-prompt → (ant only) 输出 prompt,退出  ├── --claude-in-chrome-mcp → 启动 Chrome MCP Server  ├── --chrome-native-host → 启动 Chrome Native Host  ├── --computer-use-mcp → 启动 Computer Use MCP  ├── --daemon-worker → 启动 Daemon Worker  ├── --remote-control/bridge → 启动 Bridge 环境  ├── daemon [subcommand] → 管理 Daemon  ├── ps/logs/attach/kill → 管理会话  │  └── (其他所有情况) → 加载完整应用 → main.tsx

4.3 main.tsx:800KB 的粘合层

main.tsx 是整个应用的中枢。它有 ~4,700 行代码,不是因为它有很多业务逻辑,而是因为它需要把所有模块粘合在一起

模块加载阶段的副作用(第 1-20 行)

profileCheckpoint('main_tsx_entry')    // 性能打点startMdmRawRead()                      // MDM 预取(已并行启动)startKeychainPrefetch()                // Keychain 预取(已并行启动)

导入阶段(第 21-150 行)

50+ 个内部模块被导入,包括:

  • • Commander.js(CLI 框架)
  • • React(UI)
  • • Context、Hooks、Services、Tools、Permissions 等

条件导入阶段(第 68-81 行)

Feature-gated 的模块在此条件加载:

// 只在内部构建中存在const coordinatorModule = feature('COORDINATOR_MODE')  ? require('./coordinator/coordinatorMode.js')  : nullconst kairosModule = feature('KAIROS')  ? require('./assistant/index.js')  : nullconst autoModeModule = feature('TRANSCRIPT_CLASSIFIER')  ? require('./utils/autoMode.js')  : null

main() 函数(第 585 行)

main()  → eagerLoadSettings()    // 早期设置加载(在 init 之前)  → run()                  // 启动 Commander.js 程序

run() 函数(第 884 行,最核心)

const program = new Command('claude')  .description('Claude Code - AI Agent')  .option('--debug', '调试模式')  .option('--print', '非交互模式')  .option('--bare', '最小模式')  .option('--model <model>', '指定模型')  // ... 数十个选项

preAction 钩子:真正的初始化入口

Commander.js 的 preAction 钩子在每个命令执行前触发。这是延迟初始化的关键 — 只有当用户确实要执行命令时才做重量级初始化:

preAction 钩子执行序列:  1. await MDM 预取完成  2. await Keychain 预取完成  3. init()                    ← 17 步初始化(见 4.4)  4. process.title = 'claude'  5. initSinks()               ← 日志初始化  6. 加载 --plugin-dir 插件  7. runMigrations()           ← 配置迁移(见 4.5)  8. 加载远程管理设置(非阻塞)  9. 上传用户设置(后台)

注册的子命令

claude mcp [subcommand]    — MCP 服务器管理claude plugin [subcommand] — 插件管理claude auth [subcommand]   — 认证管理claude doctor              — 健康检查claude config              — 配置管理claude daemon [subcommand] — 守护进程管理claude remote-control      — 远程控制claude ps/logs/attach/kill — 会话管理

4.4 init.ts:17 步初始化序列

init() 是记忆化的 — 只执行一次,后续调用直接返回。

init() 执行序列(按严格顺序): ① enableConfigs()   │  加载 settings.json 配置系统   ▼ ② applySafeConfigEnvironmentVariables()   │  应用安全的环境变量(在信任对话框之前)   ▼ ③ applyExtraCACertsFromConfig()   │  加载额外的 CA 证书(在任何 TLS 握手之前)   ▼ ④ setupGracefulShutdown()   │  注册优雅关闭处理器(SIGINT/SIGTERM)   ▼ ⑤ 1P 事件日志(fire-and-forget)   │  第一方分析事件,异步发送   ▼ ⑥ GrowthBook 刷新处理器   │  Feature Flag 定期刷新   ▼ ⑦ populateOAuthAccountInfoIfNeeded()   │  OAuth 账户信息填充(异步)   ▼ ⑧ initJetBrainsDetection()   │  JetBrains IDE 检测(异步)   ▼ ⑨ detectCurrentRepository()   │  Git 仓库检测(异步)   ▼ ⑩ 初始化远程管理设置加载 Promise   │  企业设置异步加载   ▼ ⑪ 初始化策略限制加载 Promise   │  组织策略异步加载   ▼ ⑫ recordFirstStartTime()   │  记录首次启动时间   ▼ ⑬ configureGlobalMTLS()   │  mTLS 证书配置   ▼ ⑭ configureGlobalAgents()   │  代理和 mTLS 配置   ▼ ⑮ preconnectAnthropicApi()   │  TCP+TLS 预热连接(减少首次 API 调用延迟)   ▼ ⑯ 上游代理初始化(仅 CCR 容器)   │  CONNECT 中继,供子进程使用   ▼ ⑰ 清理注册      LSP 服务器关闭、团队清理等

顺序的重要性

初始化步骤的顺序是精心设计的,每步都有前置依赖:

CA 证书(③) 必须在 TLS 握手前完成  → 所以 API 预连接(⑮) 在它之后环境变量(②) 必须在信任对话框前应用  → 所以配置加载(①) 在最前面优雅关闭(④) 要尽早注册  → 确保任何阶段的中断都能正确清理

4.5 迁移系统:管理配置演进

为什么需要迁移?

Claude Code 的配置和设置会随版本演化。例如:

  • • 模型名称变更:fennec → opussonnet-1m → sonnet-4.5 → sonnet-4.6
  • • 设置迁移:某个选项从 A 位置移到 B 位置
  • • 默认值重置:Pro 用户的默认模型重置为 Opus

迁移文件列表

migrations/├── migrateAutoUpdatesToSettings.ts              — 自动更新首选项移至 settings.json├── migrateLegacyOpusToCurrent.ts                — Opus 4.0/4.1 重映射为 'opus'├── migrateOpusToOpus1m.ts                       — Opus → Opus 1M├── migrateSonnet1mToSonnet45.ts                 — Sonnet 1M → Sonnet 4.5├── migrateSonnet45ToSonnet46.ts                 — Sonnet 4.5 → Sonnet 4.6├── migrateFennecToOpus.ts                       — Fennec(内部代号) → Opus (ant only)├── migrateBypassPermissionsAcceptedToSettings.ts — 权限绕过设置迁移├── migrateEnableAllProjectMcpServersToSettings.ts — MCP 服务器设置迁移├── migrateReplBridgeEnabledToRemoteControlAtStartup.ts — Bridge 模式迁移├── resetAutoModeOptInForDefaultOffer.ts          — 自动模式选项重置└── resetProToOpusDefault.ts                      — Pro 用户默认模型重置

迁移的执行结构

每个迁移是一个函数,遵循相同模式:

// 典型迁移模式(简化)export async function migrateSonnet45ToSonnet46() {  // 1. 检查条件(如 API 提供者、当前设置)  const settings = getSettingsForSource('user')  if (settings.model !== 'sonnet-4.5') return  // 不需要迁移  // 2. 执行迁移  updateSettingsForSource('user', {    ...settings,    model: 'sonnet-4.6'  })  // 3. 记录迁移事件  logEvent('migration_applied', { name: 'sonnet45_to_sonnet46' })  // 4. 标记迁移版本  saveMigrationVersion(Date.now())}

从迁移中读取产品历史

迁移文件名本身就是一部产品演化史:

时间线 ────────────────────────────────────────────►Fennec(内部代号)  →  Opus  →  Opus 1M                    Sonnet 1M  →  Sonnet 4.5  →  Sonnet 4.6

Fennec 是 Opus 的内部动物代号,正如 Tengu 是 Claude Code 项目的代号。这类代号出现在内部构建中但不应泄露到外部 — 这正是 Undercover 模式要防护的内容。


4.6 MCP Server 入口

entrypoints/mcp.ts 让 Claude Code 作为 MCP 服务器运行,供其他应用(如 IDE 插件)调用:

// 简化的 MCP 入口import { Server } from '@modelcontextprotocol/sdk'const server = new Server()// 注册工具列表请求处理器server.setRequestHandler(ListToolsRequestSchema, async () => {  const tools = getTools({ permissionContext: empty })  return { tools: tools.map(t => t.toJsonSchema()) }})// 注册工具调用请求处理器server.setRequestHandler(CallToolRequestSchema, async (req) => {  const tool = findToolByName(req.name)  return await tool.call(req.input)})// 以 stdio 模式运行server.run({ transport: new StdioTransport() })

与 CLI 的关键差异:

  • • 无 React UI — 纯 API 模式
  • • 无交互式权限弹窗 — 使用空权限上下文
  • • 无 REPL — 响应式而非交互式
  • • 更轻的初始化 — 不需要终端渲染相关模块

4.7 完整启动时序图

时间 ─────────────────────────────────────────────────────────────►cli.tsx 加载├── 快速路径检查 (--version, daemon, ps...)│     └── 匹配?→ 立即执行 → 退出├── 并行启动                              ← 模块加载副作用│   ├── MDM 配置读取 ─────────────────┐│   ├── Keychain 预取 ────────────────┤│   └── 模块导入 ─────────────────────┤│                                      │main.tsx 加载                           │├── 性能打点                            ││                                      │main() 执行                             │├── eagerLoadSettings()                ││                                      │run() 执行                              │├── Commander.js 程序创建               │├── 注册选项和子命令                     ││                                      │preAction 钩子 ◄───────────────────────┘  ← 等待预取完成├── ① await MDM 完成├── ② await Keychain 完成├── ③ init()│   ├── 配置系统启用│   ├── 环境变量应用│   ├── CA 证书加载│   ├── 优雅关闭注册│   ├── 分析/GrowthBook 初始化│   ├── OAuth/IDE/仓库检测(异步)│   ├── mTLS/代理配置│   └── API 预连接├── ④ initSinks() — 日志初始化├── ⑤ 加载插件├── ⑥ runMigrations() — 配置迁移├── ⑦ 远程设置加载(非阻塞)action 处理器├── 认证检查├── MCP 配置加载├── 工具初始化├── React REPL 渲染REPL 就绪 ✓├── startDeferredPrefetches()  ← 首次渲染后的后台预取│   ├── 进程预热│   ├── 缓存预热│   └── 跳过 bare 模式等待用户输入...

课后练习

练习 1:快速路径清单

阅读 cli.tsx 的前 200 行,列出所有快速路径(不需要完整初始化的命令路径)。思考:为什么这些命令被设计为快速路径?

练习 2:初始化依赖分析

在 init.ts 的 17 步中,找出哪些步骤之间有严格的顺序依赖(A 必须在 B 之前),哪些步骤可以并行执行。画出一个最优并行化方案。

练习 3:迁移考古

阅读 3 个迁移文件的实际代码,回答:

  • • 迁移如何知道自己已经被执行过?
  • • 如果迁移失败,会怎样?有回滚机制吗?

练习 4:入口点对比

对比 CLI、MCP、SDK 三个入口点的初始化流程差异,制作一张对比表。


本课小结

要点
内容
三个入口
CLI(交互式)、MCP(服务器)、SDK(库)
快速路径
--version 等简单命令绕过完整初始化
并行预取
MDM + Keychain + 模块导入同时进行
延迟初始化
preAction 钩子确保只在需要时初始化
17 步 init
配置 → 证书 → 网络 → 检测 → 预连接
迁移系统
11 个迁移脚本管理配置演进,记录产品历史

下一课预告

第 05 课:状态管理架构 — 深入 AppState 的完整形状定义、Store<T> 泛型模式的实现、不可变更新策略,以及状态变更如何触发 UI 重渲染。