第 04 课:入口与启动流程

4 阅读8分钟

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


学习目标

完成本课后,你将能够:

  1. 画出从 cli.tsx 到 REPL 就绪的完整启动时序图
  2. 说明三个入口点(CLI/MCP/SDK)的差异
  3. 列出 init.ts 的 17 步初始化序列及其目的
  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 worker
claude daemon [cmd]  → 直接管理 daemon
claude 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')
  : null

const kairosModule = feature('KAIROS')
  ? require('./assistant/index.js')
  : null

const 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 的配置和设置会随版本演化。例如:

  • 模型名称变更:fennecopussonnet-1msonnet-4.5sonnet-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.tsFennec(内部代号) → 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 重渲染。