Skip to content

Latest commit

 

History

History
1219 lines (1000 loc) · 37.5 KB

File metadata and controls

1219 lines (1000 loc) · 37.5 KB

Claude Code v2.1.88 - Bootstrap and Initialization System

Architecture Diagram

Startup Sequence

1. Architecture Overview

Bootstrap 是 Claude Code 的启动临界路径, 从进程入口到 REPL 渲染第一帧. 整体设计遵循 "fast-path 短路 + lazy import + parallel prefetch" 三大原则, 确保 --version 零 依赖输出, 交互启动 <300ms 首帧渲染.

 process.argv
     |
     v
+--------------------------------------------+
|           cli.tsx (entrypoint)              |
|  Top-level side-effects:                   |
|    COREPACK_ENABLE_AUTO_PIN=0              |
|    CCR max-heap 8192                       |
|    Ablation baseline env vars              |
|                                            |
|  Fast-path dispatch:                       |
|    --version/-v/-V  --> console.log, exit  |
|    --daemon-worker  --> workerRegistry     |
|    bridge/remote    --> bridgeMain         |
|    daemon           --> daemonMain         |
|    ps/logs/attach/kill/--bg --> bg.js      |
|    new/list/reply   --> templateJobs       |
|    environment-runner --> envRunnerMain    |
|    self-hosted-runner --> shRunnerMain     |
|    --worktree+--tmux --> execIntoTmux      |
|                                            |
|  Normal path:                              |
|    startCapturingEarlyInput()              |
|    dynamic import('../main.js')            |
|    await cliMain()                         |
+--------------------------------------------+
     |
     v
+--------------------------------------------+
|           main.tsx (full init)              |
|  Module-eval side-effects:                 |
|    profileCheckpoint('main_tsx_entry')      |
|    startMdmRawRead()    // MDM prefetch    |
|    startKeychainPrefetch() // macOS        |
|                                            |
|  main() function:                          |
|    warning handler + SIGINT                |
|    deep-link URI dispatch                  |
|    assistant/ssh argv rewriting            |
|    detect interactive/non-interactive      |
|    eagerLoadSettings()                     |
|    run() --> Commander program             |
+--------------------------------------------+
     |
     v
+--------------------------------------------+
|      Commander preAction hook              |
|  ensureMdmSettingsLoaded()                 |
|  ensureKeychainPrefetchCompleted()         |
|  init()  (memoized, once)                  |
|  initSinks()                               |
|  runMigrations()                           |
|  loadRemoteManagedSettings()               |
|  loadPolicyLimits()                        |
+--------------------------------------------+
     |
     v
+--------------------------------------------+
|    Default command .action() handler       |
|  setup() + parallel cmd/agent loading      |
|  MCP config loading                        |
|  showSetupScreens() (trust/auth/onboard)   |
|  model resolution + tool permission init   |
|  session resume (--continue/--resume)      |
|  launchRepl()                              |
+--------------------------------------------+

2. CLI Entry Point Fast-Paths

File: src/entrypoints/cli.tsx

2.1 Top-Level Side-Effects (Module Eval)

main() 函数执行之前, 三个 side-effect 在模块求值阶段执行:

// 1. 阻止 corepack 修改 package.json
process.env.COREPACK_ENABLE_AUTO_PIN = '0'

// 2. CCR 环境设置 8GB 堆上限
if (process.env.CLAUDE_CODE_REMOTE === 'true') {
  process.env.NODE_OPTIONS = existing
    ? `${existing} --max-old-space-size=8192`
    : '--max-old-space-size=8192'
}

// 3. Ablation baseline (ant-only, feature-gated DCE)
if (feature('ABLATION_BASELINE') && process.env.CLAUDE_CODE_ABLATION_BASELINE) {
  // 设置 CLAUDE_CODE_SIMPLE, DISABLE_THINKING, DISABLE_COMPACT 等
}

2.2 Fast-Path 分发表

所有 import 均为 dynamic import, 保证未匹配路径零模块加载开销:

优先级 匹配条件 目标模块 需要 enableConfigs Profiler Checkpoint
1 --version, -v, -V 无 (MACRO.VERSION 编译时内联)
2 --dump-system-prompt constants/prompts.js cli_dump_system_prompt_path
3 --claude-in-chrome-mcp claudeInChrome/mcpServer.js cli_claude_in_chrome_mcp_path
4 --chrome-native-host claudeInChrome/chromeNativeHost.js cli_chrome_native_host_path
5 --computer-use-mcp computerUse/mcpServer.js cli_computer_use_mcp_path
6 --daemon-worker daemon/workerRegistry.js
7 remote-control/rc/bridge/sync bridge/bridgeMain.js cli_bridge_path
8 daemon daemon/main.js cli_daemon_path
9 ps/logs/attach/kill/--bg cli/bg.js cli_bg_path
10 new/list/reply cli/handlers/templateJobs.js cli_templates_path
11 environment-runner environment-runner/main.js cli_environment_runner_path
12 self-hosted-runner self-hosted-runner/main.js cli_self_hosted_runner_path
13 --worktree + --tmux utils/worktree.js cli_tmux_worktree_fast_path

2.3 --version 零开销路径

// 零 import, 零 module load
if (args.length === 1 && (args[0] === '--version' || args[0] === '-v' || args[0] === '-V')) {
  console.log(`${MACRO.VERSION} (Claude Code)`)
  return
}

MACRO.VERSION 在构建时由 Bun 内联为字符串常量, 该路径不触发任何 import().

2.4 --daemon-worker 轻量路径

if (feature('DAEMON') && args[0] === '--daemon-worker') {
  const { runDaemonWorker } = await import('../daemon/workerRegistry.js')
  await runDaemonWorker(args[1])  // args[1] = worker kind
  return
}

daemon-worker 是 supervisor 内部 fork 出的子进程, 不调用 enableConfigs(), 不初始化 analytics sinks -- worker 是精简进程. 如果 worker kind 需要 config/auth (如 assistant worker), 由 worker 自身的 run() 函数内部调用.

2.5 Bridge 模式 (Remote Control)

Bridge 路径有完整的认证和策略检查链:

enableConfigs() --> getClaudeAIOAuthTokens() 检查 accessToken
    --> getBridgeDisabledReason() (等待 GrowthBook 初始化)
    --> checkBridgeMinVersion() 版本兼容性
    --> waitForPolicyLimitsToLoad() + isPolicyAllowed('allow_remote_control')
    --> bridgeMain(args.slice(1))

2.6 Session Management (BG_SESSIONS)

// ps/logs/attach/kill 子命令, 或 --bg/--background flag
if (feature('BG_SESSIONS') && (args[0] === 'ps' || ...)) {
  enableConfigs()
  const bg = await import('../cli/bg.js')
  switch (args[0]) {
    case 'ps':    await bg.psHandler(args.slice(1)); break
    case 'logs':  await bg.logsHandler(args[1]); break
    case 'attach': await bg.attachHandler(args[1]); break
    case 'kill':  await bg.killHandler(args[1]); break
    default:      await bg.handleBgFlag(args)  // --bg/--background
  }
}

2.7 Normal Path 入口

未匹配任何 fast-path 后, 进入完整 CLI 启动:

// 1. 开始捕获用户早期输入 (防止丢失 REPL 渲染前的按键)
const { startCapturingEarlyInput } = await import('../utils/earlyInput.js')
startCapturingEarlyInput()

// 2. 动态导入 main.tsx (触发大量模块求值)
profileCheckpoint('cli_before_main_import')
const { main: cliMain } = await import('../main.js')
profileCheckpoint('cli_after_main_import')

// 3. 执行主流程
await cliMain()
profileCheckpoint('cli_after_main_complete')

3. main.tsx 初始化序列

File: src/main.tsx

3.1 Module-Eval Side-Effects (Import 时执行)

main.tsx 的前 20 行在模块求值期间触发三个关键 side-effect, 利用 import 加载时间做 并行预取:

// Side-effect 1: 标记入口时间点
import { profileCheckpoint } from './utils/startupProfiler.js'
profileCheckpoint('main_tsx_entry')

// Side-effect 2: 启动 MDM 子进程读取 (plutil/reg query)
import { startMdmRawRead } from './utils/settings/mdm/rawRead.js'
startMdmRawRead()
// 与后续 ~135ms 的 import 并行执行

// Side-effect 3: 启动 macOS keychain 并行读取
import { startKeychainPrefetch } from './utils/secureStorage/keychainPrefetch.js'
startKeychainPrefetch()
// OAuth token (~32ms) + legacy API key (~33ms) 并行 vs 串行 ~65ms

在所有 static import 完成后:

profileCheckpoint('main_tsx_imports_loaded')

3.2 main() 函数流程

export async function main() {
  profileCheckpoint('main_function_start')

  // 1. Windows 安全: 阻止从当前目录执行命令
  process.env.NoDefaultCurrentDirectoryInExePath = '1'

  // 2. 初始化 warning handler + SIGINT 处理
  initializeWarningHandler()
  process.on('exit', () => resetCursor())
  process.on('SIGINT', () => { /* print 模式由 print.ts 处理 */ })

  // 3. Deep-link URI 处理 (LODESTONE feature gate)
  //    --handle-uri <uri> --> handleDeepLinkUri() --> process.exit()
  //    macOS LaunchServices (__CFBundleIdentifier) --> handleUrlSchemeLaunch()

  // 4. Assistant 模式 argv 重写
  //    `claude assistant [sessionId]` --> 提取参数, 重写 argv

  // 5. SSH 远程 argv 重写
  //    `claude ssh <host> [dir]` --> 提取 host/cwd/flags, 重写 argv

  // 6. 检测交互/非交互模式
  const isNonInteractive = hasPrintFlag || hasInitOnlyFlag || hasSdkUrl || !process.stdout.isTTY
  setIsInteractive(!isNonInteractive)

  // 7. 确定 entrypoint 类型
  initializeEntrypoint(isNonInteractive)  // 设置 CLAUDE_CODE_ENTRYPOINT

  // 8. 确定 clientType
  //    github-action | sdk-typescript | sdk-python | sdk-cli
  //    claude-vscode | local-agent | claude-desktop | remote | cli
  setClientType(clientType)

  // 9. 提前加载 --settings 和 --setting-sources
  eagerLoadSettings()

  // 10. 进入 Commander 命令解析
  await run()
}

3.3 Client Type 判断逻辑

const clientType = (() => {
  if (isEnvTruthy(process.env.GITHUB_ACTIONS))         return 'github-action'
  if (process.env.CLAUDE_CODE_ENTRYPOINT === 'sdk-ts')  return 'sdk-typescript'
  if (process.env.CLAUDE_CODE_ENTRYPOINT === 'sdk-py')  return 'sdk-python'
  if (process.env.CLAUDE_CODE_ENTRYPOINT === 'sdk-cli') return 'sdk-cli'
  if (process.env.CLAUDE_CODE_ENTRYPOINT === 'claude-vscode') return 'claude-vscode'
  if (process.env.CLAUDE_CODE_ENTRYPOINT === 'local-agent')   return 'local-agent'
  if (process.env.CLAUDE_CODE_ENTRYPOINT === 'claude-desktop') return 'claude-desktop'
  if (hasSessionIngressToken)                            return 'remote'
  return 'cli'
})()

3.4 Commander preAction Hook

所有子命令共享的初始化逻辑:

program.hook('preAction', async (thisCommand) => {
  // 1. 等待预取完成 (几乎零成本 - 子进程在 import 期间已完成)
  await Promise.all([
    ensureMdmSettingsLoaded(),
    ensureKeychainPrefetchCompleted()
  ])

  // 2. 核心初始化 (memoized, 仅执行一次)
  await init()  // --> src/entrypoints/init.ts

  // 3. 设置终端标题
  process.title = 'claude'

  // 4. 初始化日志 sinks
  initSinks()

  // 5. 处理 --plugin-dir
  setInlinePlugins(pluginDir)

  // 6. 执行迁移
  runMigrations()  // CURRENT_MIGRATION_VERSION = 11

  // 7. 加载远程托管配置 (非阻塞)
  void loadRemoteManagedSettings()
  void loadPolicyLimits()
})

3.5 Default Action Handler 关键阶段

action_handler_start
    |
    v
kairos/assistant 检测 & 初始化
    |
    v
解析所有 CLI options
    |
    v
工具权限上下文构建 (permissionMode, autoMode, toolPermissionContext)
    |
    v
assertMinVersion() + MCP config 预加载
    |
    v
action_before_setup
    |
    v
setup() + 并行加载 commands + agents  [~28ms]
    |
    v
action_after_setup
    |
    v
GrowthBook 初始化 + analytics 事件注册
    |
    v
showSetupScreens() (trust dialog / OAuth / onboarding)
    |
    v
LSP manager 初始化 (trust 建立后)
    |
    v
模型解析 + thinking config
    |
    v
session resume (--continue / --resume / --from-pr)
    |
    v
launchRepl()

4. Bootstrap State Management

File: src/bootstrap/state.ts

Bootstrap state 是整个应用的全局单例状态, 通过闭包模块模式封装, 仅暴露 getter/setter 函数. 设计原则: bootstrap 模块是 import DAG 的叶节点, 不得引入业务模块依赖.

4.1 State 类型定义

type State = {
  // === 路径状态 ===
  originalCwd: string          // 启动时的工作目录 (symlink resolved, NFC normalized)
  projectRoot: string          // 稳定项目根 - 启动时设置一次, 不受 EnterWorktreeTool 影响
  cwd: string                  // 当前工作目录 (可随 worktree/chdir 变化)

  // === 成本与性能追踪 ===
  totalCostUSD: number
  totalAPIDuration: number
  totalAPIDurationWithoutRetries: number
  totalToolDuration: number
  turnHookDurationMs: number
  turnToolDurationMs: number
  turnClassifierDurationMs: number
  turnToolCount: number
  turnHookCount: number
  turnClassifierCount: number
  startTime: number            // Date.now() at init
  lastInteractionTime: number

  // === 代码变更追踪 ===
  totalLinesAdded: number
  totalLinesRemoved: number

  // === 模型状态 ===
  modelUsage: { [modelName: string]: ModelUsage }
  mainLoopModelOverride: ModelSetting | undefined
  initialMainLoopModel: ModelSetting   // CLI/SDK 指定的模型
  modelStrings: ModelStrings | null
  hasUnknownModelCost: boolean

  // === 会话状态 ===
  isInteractive: boolean
  kairosActive: boolean        // assistant 模式激活
  strictToolResultPairing: boolean  // HFI: mismatch 时抛异常而非修复
  userMsgOptIn: boolean
  clientType: string           // 'cli' | 'sdk-typescript' | 'remote' | ...
  sessionSource: string | undefined
  sessionId: SessionId         // UUID, randomUUID() 初始化
  parentSessionId: SessionId | undefined

  // === SDK 状态 ===
  sdkAgentProgressSummariesEnabled: boolean
  questionPreviewFormat: 'markdown' | 'html' | undefined
  initJsonSchema: Record<string, unknown> | null
  registeredHooks: Partial<Record<HookEvent, RegisteredHookMatcher[]>> | null
  sdkBetas: string[] | undefined

  // === 配置来源 ===
  flagSettingsPath: string | undefined
  flagSettingsInline: Record<string, unknown> | null
  allowedSettingSources: SettingSource[]
  sessionIngressToken: string | null | undefined
  oauthTokenFromFd: string | null | undefined
  apiKeyFromFd: string | null | undefined

  // === Telemetry (OTel) ===
  meter: Meter | null
  sessionCounter: AttributedCounter | null
  locCounter: AttributedCounter | null
  prCounter: AttributedCounter | null
  commitCounter: AttributedCounter | null
  costCounter: AttributedCounter | null
  tokenCounter: AttributedCounter | null
  codeEditToolDecisionCounter: AttributedCounter | null
  activeTimeCounter: AttributedCounter | null
  statsStore: { observe(name: string, value: number): void } | null
  loggerProvider: LoggerProvider | null
  eventLogger: ReturnType<typeof logs.getLogger> | null
  meterProvider: MeterProvider | null
  tracerProvider: BasicTracerProvider | null

  // === Agent 状态 ===
  agentColorMap: Map<string, AgentColorName>
  agentColorIndex: number
  mainThreadAgentType: string | undefined

  // === API 调试 ===
  lastAPIRequest: Omit<BetaMessageStreamParams, 'messages'> | null
  lastAPIRequestMessages: BetaMessageStreamParams['messages'] | null
  lastClassifierRequests: unknown[] | null
  cachedClaudeMdContent: string | null
  inMemoryErrorLog: Array<{ error: string; timestamp: string }>

  // === Plugin/Skill 状态 ===
  inlinePlugins: Array<string>       // --plugin-dir
  chromeFlagOverride: boolean | undefined
  useCoworkPlugins: boolean
  invokedSkills: Map<string, { skillName: string; skillPath: string; content: string; ... }>

  // === 权限状态 ===
  sessionBypassPermissionsMode: boolean
  sessionTrustAccepted: boolean

  // === Session 持久化 ===
  sessionPersistenceDisabled: boolean
  sessionProjectDir: string | null

  // === Plan Mode ===
  hasExitedPlanMode: boolean
  needsPlanModeExitAttachment: boolean
  planSlugCache: Map<string, string>

  // === Teleport ===
  teleportedSessionInfo: { isTeleported: boolean; hasLoggedFirstMessage: boolean; sessionId: string | null } | null

  // === Cache/Latch 状态 ===
  promptCache1hAllowlist: string[] | null
  promptCache1hEligible: boolean | null
  afkModeHeaderLatched: boolean | null
  fastModeHeaderLatched: boolean | null
  cacheEditingHeaderLatched: boolean | null
  thinkingClearLatched: boolean | null
  promptId: string | null
  lastMainRequestId: string | undefined
  lastApiCompletionTimestamp: number | null
  pendingPostCompaction: boolean

  // === Channel/Remote ===
  allowedChannels: ChannelEntry[]
  hasDevChannels: boolean
  directConnectServerUrl: string | undefined
  isRemoteMode: boolean
  additionalDirectoriesForClaudeMd: string[]

  // === 其他 ===
  systemPromptSectionCache: Map<string, string | null>
  lastEmittedDate: string | null
  scheduledTasksEnabled: boolean
  sessionCronTasks: SessionCronTask[]
  sessionCreatedTeams: Set<string>
  lspRecommendationShownThisSession: boolean
  slowOperations: Array<{ operation: string; durationMs: number; timestamp: number }>
  needsAutoModeExitAttachment: boolean
}

4.2 初始状态工厂

function getInitialState(): State {
  // Resolve symlinks to match shell.ts setCwd behavior
  let resolvedCwd = ''
  const rawCwd = cwd()
  try {
    resolvedCwd = realpathSync(rawCwd).normalize('NFC')
  } catch {
    resolvedCwd = rawCwd.normalize('NFC')  // EPERM on CloudStorage mounts
  }

  return {
    originalCwd: resolvedCwd,
    projectRoot: resolvedCwd,
    cwd: resolvedCwd,
    sessionId: randomUUID() as SessionId,
    startTime: Date.now(),
    lastInteractionTime: Date.now(),
    clientType: 'cli',
    allowedSettingSources: ['userSettings', 'projectSettings', 'localSettings',
                           'flagSettings', 'policySettings'],
    // ... (所有字段初始为零值/null/空集合)
  }
}

const STATE: State = getInitialState()

4.3 关键 Getter/Setter 签名

// Session 管理
export function getSessionId(): SessionId
export function regenerateSessionId(options?: { setCurrentAsParent?: boolean }): SessionId
export function switchSession(sessionId: SessionId, projectDir?: string | null): void
export function getParentSessionId(): SessionId | undefined
export function getSessionProjectDir(): string | null
export const onSessionSwitch: (cb: (id: SessionId) => void) => () => void

// 路径管理
export function getOriginalCwd(): string
export function setOriginalCwd(cwd: string): void  // NFC normalize
export function getProjectRoot(): string
export function setProjectRoot(cwd: string): void   // 仅 --worktree startup
export function getCwdState(): string
export function setCwdState(cwd: string): void

// 成本/性能
export function addToTotalCostState(cost: number, usage: ModelUsage, model: string): void
export function getTotalCostUSD(): number
export function getTotalAPIDuration(): number
export function addToToolDuration(duration: number): void
export function addToTurnHookDuration(duration: number): void

// Token 统计
export function getTotalInputTokens(): number   // sumBy(modelUsage, 'inputTokens')
export function getTotalOutputTokens(): number
export function getTotalCacheReadInputTokens(): number
export function getTotalCacheCreationInputTokens(): number

// 交互时间 (懒刷新, 批量化 Date.now())
export function updateLastInteractionTime(immediate?: boolean): void
export function flushInteractionTime(): void  // Ink render 前调用

4.4 Session 切换机制

switchSession() 是 session ID 和 project dir 的原子切换:

export function switchSession(
  sessionId: SessionId,
  projectDir: string | null = null
): void {
  STATE.planSlugCache.delete(STATE.sessionId)  // 清理旧 session 的 plan slug
  STATE.sessionId = sessionId
  STATE.sessionProjectDir = projectDir
  sessionSwitched.emit(sessionId)  // Signal 通知订阅者 (如 concurrentSessions)
}

regenerateSessionId() 用于 plan mode -> implementation 切换:

export function regenerateSessionId(
  options: { setCurrentAsParent?: boolean } = {}
): SessionId {
  if (options.setCurrentAsParent) {
    STATE.parentSessionId = STATE.sessionId  // 保留父子关系
  }
  STATE.planSlugCache.delete(STATE.sessionId)
  STATE.sessionId = randomUUID() as SessionId
  STATE.sessionProjectDir = null
  return STATE.sessionId
}

4.5 ChannelEntry 类型

export type ChannelEntry =
  | { kind: 'plugin'; name: string; marketplace: string; dev?: boolean }
  | { kind: 'server'; name: string; dev?: boolean }

kind: 'plugin' 通过 marketplace 验证 + allowlist; kind: 'server' 的 allowlist 始终 失败 (schema 仅限 plugin).


5. Startup Profiling System

File: src/utils/startupProfiler.ts

5.1 采样策略

const DETAILED_PROFILING = isEnvTruthy(process.env.CLAUDE_CODE_PROFILE_STARTUP)
const STATSIG_SAMPLE_RATE = 0.005  // 0.5% 外部用户
const STATSIG_LOGGING_SAMPLED =
  process.env.USER_TYPE === 'ant' || Math.random() < STATSIG_SAMPLE_RATE
const SHOULD_PROFILE = DETAILED_PROFILING || STATSIG_LOGGING_SAMPLED

两种模式:

  • 采样日志: 100% ant 用户, 0.5% 外部用户, 各阶段时间上报 Statsig
  • 详细 profiling: CLAUDE_CODE_PROFILE_STARTUP=1, 带内存快照的完整报告

5.2 Checkpoint 序列

profiler_initialized
cli_entry
main_tsx_entry
main_tsx_imports_loaded          // ~135ms from main_tsx_entry
main_function_start
main_warning_handler_initialized
main_client_type_determined
eagerLoadSettings_start
eagerLoadSettings_end
main_before_run
run_function_start
run_commander_initialized
preAction_start
preAction_after_mdm
preAction_after_init
preAction_after_sinks
preAction_after_migrations
preAction_after_remote_settings
preAction_after_settings_sync
action_handler_start
action_before_setup
action_after_setup
action_after_input_prompt
cli_after_main_complete

5.3 Phase Definitions (Statsig 上报)

const PHASE_DEFINITIONS = {
  import_time:   ['cli_entry', 'main_tsx_imports_loaded'],
  init_time:     ['init_function_start', 'init_function_end'],
  settings_time: ['eagerLoadSettings_start', 'eagerLoadSettings_end'],
  total_time:    ['cli_entry', 'main_after_run'],
}

5.4 profileCheckpoint 实现

export function profileCheckpoint(name: string): void {
  if (!SHOULD_PROFILE) return
  const perf = getPerformance()
  perf.mark(name)
  if (DETAILED_PROFILING) {
    memorySnapshots.push(process.memoryUsage())
  }
}

6. Prefetch Strategy

6.1 MDM Prefetch

File: src/utils/settings/mdm/rawRead.ts

在 main.tsx 模块求值阶段, startMdmRawRead() 启动 MDM 设置子进程:

macOS:  plutil -convert json -o - /path/to/plist  (可能多个 plist)
Windows: reg query HKLM\...\Anthropic /v Settings + HKCU\...

子进程在 ~135ms 的 import 期间并行运行, 结果通过 getMdmRawReadPromise()ensureMdmSettingsLoaded() (preAction hook) 中消费.

export type RawReadResult = {
  plistStdouts: Array<{ stdout: string; label: string }> | null
  hklmStdout: string | null
  hkcuStdout: string | null
}

6.2 Keychain Prefetch

File: src/utils/secureStorage/keychainPrefetch.ts

macOS 专用. applySafeConfigEnvironmentVariables() 需要读取两个 keychain entry:

  1. "Claude Code-credentials" (OAuth tokens) ~32ms
  2. "Claude Code" (legacy API key) ~33ms

串行读取需要 ~65ms. 通过 startKeychainPrefetch() 在 import 阶段并行启动两个 security find-generic-password 子进程:

function spawnSecurity(serviceName: string): Promise<SpawnResult> {
  return new Promise(resolve => {
    execFile('security',
      ['find-generic-password', '-a', getUsername(), '-w', '-s', serviceName],
      { encoding: 'utf-8', timeout: KEYCHAIN_PREFETCH_TIMEOUT_MS },
      ...)
  })
}

--bare 模式跳过 keychain prefetch (仅使用 ANTHROPIC_API_KEY 或 apiKeyHelper).

6.3 MCP Prefetch

MCP 配置加载与 setup() 并行启动:

// 仅读取本地文件, 无执行 -- 安全的预加载
const mcpConfigPromise = strictMcpConfig || isBareMode()
  ? Promise.resolve({ servers: {} })
  : getClaudeCodeMcpConfigs(dynamicMcpConfig)

// claude.ai 代理服务器 fetch (-p 模式, 非 enterprise)
const claudeaiConfigPromise = isNonInteractiveSession && !strictMcpConfig
  ? fetchClaudeAIMcpConfigsIfEligible()
  : Promise.resolve({})

MCP 资源的实际预取 (prefetchAllMcpResources) 延迟到 trust dialog 之后.

6.4 Deferred Prefetches

REPL 首次渲染后启动 (startDeferredPrefetches):

export function startDeferredPrefetches(): void {
  // --bare 模式跳过所有 prefetch
  if (isBareMode()) return

  void initUser()                        // 用户信息
  void getUserContext()                   // CLAUDE.md + 上下文
  prefetchSystemContextIfSafe()          // git status (需要 trust)
  void getRelevantTips()                 // 提示信息
  void prefetchAwsCredentialsAndBedRockInfoIfSafe()  // Bedrock
  void prefetchGcpCredentialsIfSafe()    // Vertex
  void countFilesRoundedRg(getCwd(), ...)  // ripgrep 文件计数

  void initializeAnalyticsGates()
  void prefetchOfficialMcpUrls()
  void refreshModelCapabilities()
  void settingsChangeDetector.initialize()
  void skillChangeDetector.initialize()
}

7. Config Initialization Order

File: src/entrypoints/init.ts

init() 是 memoized 函数, 保证全生命周期仅执行一次:

export const init = memoize(async (): Promise<void> => {
  // Phase 1: 配置系统
  enableConfigs()
  applySafeConfigEnvironmentVariables()  // 安全的 env vars (trust 前)
  applyExtraCACertsFromConfig()          // TLS 证书 (必须在首次 TLS 前)

  // Phase 2: 退出清理
  setupGracefulShutdown()

  // Phase 3: 日志/分析 (异步)
  void Promise.all([
    import('firstPartyEventLogger.js'),
    import('growthbook.js')
  ]).then(([fp, gb]) => {
    fp.initialize1PEventLogging()
    gb.onGrowthBookRefresh(() => fp.reinitialize1PEventLoggingIfConfigChanged())
  })

  // Phase 4: Auth
  void populateOAuthAccountInfoIfNeeded()

  // Phase 5: IDE 检测
  void initJetBrainsDetection()
  void detectCurrentRepository()

  // Phase 6: 远程设置加载 Promise 初始化
  if (isEligibleForRemoteManagedSettings()) {
    initializeRemoteManagedSettingsLoadingPromise()
  }
  if (isPolicyLimitsEligible()) {
    initializePolicyLimitsLoadingPromise()
  }

  // Phase 7: 首次启动时间记录
  recordFirstStartTime()

  // Phase 8: 网络配置
  configureGlobalMTLS()        // mTLS
  configureGlobalAgents()      // proxy
  preconnectAnthropicApi()     // TCP+TLS 握手预热 (~100-200ms)

  // Phase 9: CCR upstream proxy (CLAUDE_CODE_REMOTE only)
  if (isEnvTruthy(process.env.CLAUDE_CODE_REMOTE)) {
    await initUpstreamProxy()
  }

  // Phase 10: 平台
  setShellIfWindows()          // git-bash
  registerCleanup(shutdownLspServerManager)
  registerCleanup(cleanupSessionTeams)

  // Phase 11: Scratchpad
  if (isScratchpadEnabled()) {
    await ensureScratchpadDir()
  }
})

7.1 Telemetry 初始化 (Trust 后)

export function initializeTelemetryAfterTrust(): void {
  if (isEligibleForRemoteManagedSettings()) {
    // 等待远程设置加载后再初始化 telemetry
    void waitForRemoteManagedSettingsToLoad().then(async () => {
      applyConfigEnvironmentVariables()  // 包含远程设置
      await doInitializeTelemetry()
    })
  } else {
    void doInitializeTelemetry()
  }
}

async function doInitializeTelemetry(): Promise<void> {
  if (telemetryInitialized) return
  telemetryInitialized = true
  // Lazy-load: ~400KB OpenTelemetry + protobuf
  const { initializeTelemetry } = await import('instrumentation.js')
  const meter = await initializeTelemetry()
  setMeter(meter, createAttributedCounter)
  getSessionCounter()?.add(1)
}

8. REPL Launch Process

8.1 setup() 并行化

// 1. 注册 bundled skills/plugins (纯内存, <1ms)
initBuiltinPlugins()
initBundledSkills()

// 2. 并行启动 setup + commands + agents
const setupPromise = setup(preSetupCwd, permissionMode, ...)
const commandsPromise = worktreeEnabled ? null : getCommands(preSetupCwd)
const agentDefsPromise = worktreeEnabled ? null : getAgentDefinitionsWithOverrides(preSetupCwd)

await setupPromise
// setup() ~28ms, 主要是 startUdsMessaging (socket bind ~20ms)

8.2 showSetupScreens()

在 setup() 之后, 首帧渲染前:

const onboardingShown = await showSetupScreens(
  root,
  permissionMode,
  allowDangerouslySkipPermissions,
  commands,
  enableClaudeInChrome,
  devChannels
)

showSetupScreens 包含:

  • Trust dialog (首次在项目目录运行)
  • OAuth 登录流程
  • Onboarding 引导
  • Permission mode 选择

8.3 launchRepl() 调用

File: src/replLauncher.tsx

export async function launchRepl(
  root: Root,
  appProps: {
    getFpsMetrics: () => FpsMetrics | undefined
    stats?: StatsStore
    initialState: AppState
  },
  replProps: REPLProps,
  renderAndRun: (root: Root, element: React.ReactNode) => Promise<void>
): Promise<void> {
  // 延迟加载 App + REPL 组件
  const { App } = await import('./components/App.js')
  const { REPL } = await import('./screens/REPL.js')
  await renderAndRun(root,
    <App {...appProps}>
      <REPL {...replProps} />
    </App>
  )
}

三种 launchRepl 调用场景:

  1. --continue: 加载最近会话后启动 (带 initialMessages)
  2. Direct Connect: 连接远程服务器后启动 (带 directConnectConfig)
  3. Normal: 全新会话或交互式 resume 后启动

9. Session Resume/Recovery

9.1 --continue 路径

if (options.continue) {
  const { clearSessionCaches } = await import('./commands/clear/caches.js')
  clearSessionCaches()  // 清除过期缓存

  // 加载最近的会话 (当前目录)
  const result = await loadConversationForResume(undefined, undefined)
  if (!result) {
    return await exitWithError(root, 'No conversation found to continue')
  }

  // 处理恢复的会话
  const loaded = await processResumedConversation(result, {
    forkSession: !!options.forkSession,
    includeAttribution: true,
    transcriptPath: result.fullPath
  }, resumeContext)

  await launchRepl(root, { ... }, {
    ...sessionConfig,
    initialMessages: loaded.messages,
    initialFileHistorySnapshots: loaded.fileHistorySnapshots,
    initialContentReplacements: loaded.contentReplacements,
    initialAgentName: loaded.agentName,
    initialAgentColor: loaded.agentColor,
    mainThreadAgentDefinition: loaded.restoredAgentDef ?? mainThreadAgentDefinition
  }, renderAndRun)
}

9.2 processResumedConversation

File: src/utils/sessionRestore.ts

export type ProcessedResume = {
  messages?: Message[]
  fileHistorySnapshots?: FileHistorySnapshot[]
  attributionSnapshots?: AttributionSnapshotMessage[]
  contextCollapseCommits?: ContextCollapseCommitEntry[]
  contextCollapseSnapshot?: ContextCollapseSnapshotEntry
}

恢复流程:

  1. loadConversationForResume() -- 从 transcript 文件加载原始日志
  2. 反序列化 message chain, 处理 message normalization
  3. 过滤孤立 thinking-only messages / 空白 assistant messages
  4. 恢复 file history snapshots (用于 diff 追踪)
  5. 恢复 attribution snapshots (用于 commit 归因)
  6. switchSession() 切换到恢复的 session ID
  7. 如果 forkSession=true, 使用新 session ID (保留 parent 关系)
  8. 恢复 cost state, worktree state, todo list
  9. 运行 SessionStart hooks (processSessionStartHooks)

9.3 Transcript Chain Building

Session 支持 chain 结构 -- 一个 session 可以 fork 自另一个:

// sessionStorage.ts
buildConversationChain()  // 构建完整的消息链
checkResumeConsistency()  // 验证链的完整性

9.4 --resume 交互式选择

if (options.resume) {
  if (typeof resume === 'string') {
    // UUID -- 直接恢复指定 session
  } else {
    // true -- 打开交互式选择器
    await launchResumeChooser(root, ...)
  }
}

10. SDK Entry Points

10.1 Agent SDK Types

File: src/entrypoints/agentSdkTypes.ts

公共 SDK 的类型入口, 重新导出:

  • sdk/coreTypes.ts -- 可序列化类型 (messages, configs)
  • sdk/runtimeTypes.ts -- 不可序列化类型 (callbacks, interfaces)
  • sdk/controlTypes.ts -- 控制协议类型 (@alpha)
  • sdk/settingsTypes.generated.js -- 设置类型
  • sdk/toolTypes.js -- 工具类型 (@internal)

关键函数:

// 自定义工具定义
export function tool<Schema extends AnyZodRawShape>(
  name: string,
  description: string,
  inputSchema: Schema,
  handler: (args: InferShape<Schema>, extra: unknown) => Promise<CallToolResult>,
  extras?: { annotations?: ToolAnnotations; searchHint?: string; alwaysLoad?: boolean }
): SdkMcpToolDefinition<Schema>

// 创建进程内 MCP server
export function createSdkMcpServer(options: {
  name: string
  version?: string
  tools?: Array<SdkMcpToolDefinition<any>>
}): McpSdkServerConfigWithInstance

10.2 SDK Hook Events

export const HOOK_EVENTS = [
  'PreToolUse', 'PostToolUse', 'PostToolUseFailure',
  'Notification', 'UserPromptSubmit',
  'SessionStart', 'SessionEnd',
  'Stop', 'StopFailure',
  'SubagentStart', 'SubagentStop',
  'PreCompact', 'PostCompact',
  'PermissionRequest', 'PermissionDenied',
  'Setup', 'TeammateIdle',
  'TaskCreated', 'TaskCompleted',
  'Elicitation', 'ElicitationResult',
  'ConfigChange',
  'WorktreeCreate', 'WorktreeRemove',
  'InstructionsLoaded', 'CwdChanged', 'FileChanged',
] as const

10.3 MCP Server Entry Point

File: src/entrypoints/mcp.ts

将 Claude Code 暴露为 MCP server (通过 stdio transport):

export async function startMCPServer(
  cwd: string,
  debug: boolean,
  verbose: boolean
): Promise<void>

实现:

  • ListToolsRequestSchema handler: 导出所有内置工具 (inputSchema -> zodToJsonSchema)
  • CallToolRequestSchema handler: 执行工具调用, 带权限检查
  • 额外暴露 review command 作为 MCP tool

11. Migration System

File: src/main.tsx -- runMigrations()

const CURRENT_MIGRATION_VERSION = 11

function runMigrations(): void {
  if (getGlobalConfig().migrationVersion !== CURRENT_MIGRATION_VERSION) {
    migrateAutoUpdatesToSettings()
    migrateBypassPermissionsAcceptedToSettings()
    migrateEnableAllProjectMcpServersToSettings()
    resetProToOpusDefault()
    migrateSonnet1mToSonnet45()
    migrateLegacyOpusToCurrent()
    migrateSonnet45ToSonnet46()
    migrateOpusToOpus1m()
    migrateReplBridgeEnabledToRemoteControlAtStartup()
    resetAutoModeOptInForDefaultOffer()  // feature('TRANSCRIPT_CLASSIFIER')
    migrateFennecToOpus()                // ant-only

    saveGlobalConfig(prev => ({
      ...prev,
      migrationVersion: CURRENT_MIGRATION_VERSION
    }))
  }

  // 异步迁移 (非阻塞)
  migrateChangelogFromConfig().catch(() => {})
}

12. 完整启动时间线

T=0ms     process start
T=0ms     cli.tsx module eval (COREPACK, CCR heap, ablation)
T=0ms     main() -> args check
T=~1ms    profileCheckpoint('cli_entry')
T=~1ms    dynamic import('../main.js') starts
T=~1ms    main.tsx module-eval: profileCheckpoint('main_tsx_entry')
T=~1ms    startMdmRawRead()      --> plutil/reg query subprocess
T=~2ms    startKeychainPrefetch() --> 2x security subprocess (macOS)
T=~2ms    ... 135ms of static imports ...
T=~137ms  profileCheckpoint('main_tsx_imports_loaded')
T=~138ms  main() -> initializeWarningHandler, SIGINT
T=~139ms  detect interactive, set clientType
T=~140ms  eagerLoadSettings (--settings, --setting-sources)
T=~141ms  run() -> Commander init
T=~142ms  preAction hook fires:
T=~142ms    ensureMdmSettingsLoaded()      // ~free (subprocess done)
T=~142ms    ensureKeychainPrefetchCompleted() // ~free
T=~143ms    init() starts
T=~143ms      enableConfigs()
T=~144ms      applySafeConfigEnvironmentVariables()
T=~145ms      setupGracefulShutdown()
T=~146ms      mTLS + proxy config
T=~147ms      preconnectAnthropicApi()     // TCP+TLS overlap
T=~150ms    init() ends
T=~151ms    initSinks()
T=~152ms    runMigrations()
T=~153ms    loadRemoteManagedSettings() (async)
T=~155ms  action handler starts
T=~160ms    setup() + parallel cmds/agents
T=~188ms    setup() done (~28ms)
T=~190ms    showSetupScreens() (may block for user input)
T=???ms     trust accepted
T=???ms     model resolution
T=???ms     launchRepl()
T=???ms     REPL first render
T=???ms     startDeferredPrefetches()

13. 关键文件索引

文件路径 职责
src/entrypoints/cli.tsx CLI bootstrap, fast-path 分发
src/main.tsx 完整初始化流程, Commander 配置, REPL 启动
src/bootstrap/state.ts 全局状态单例, getter/setter 导出
src/entrypoints/init.ts init() memoized 初始化, telemetry
src/entrypoints/mcp.ts MCP server 入口
src/entrypoints/agentSdkTypes.ts Agent SDK 公共类型
src/entrypoints/sdk/coreTypes.ts SDK 核心类型 (从 Zod schema 生成)
src/entrypoints/sdk/controlSchemas.ts SDK 控制协议 schema
src/utils/startupProfiler.ts 启动性能 profiling
src/utils/settings/mdm/rawRead.ts MDM 设置预读取
src/utils/secureStorage/keychainPrefetch.ts macOS keychain 预读取
src/replLauncher.tsx REPL 组件懒加载 + 渲染
src/utils/conversationRecovery.ts 会话恢复逻辑
src/utils/sessionRestore.ts 会话状态恢复
src/setup.ts setup() 函数 (worktree, cwd, UDS)
src/interactiveHelpers.ts showSetupScreens, renderAndRun