本章总览
entrypoints/cli.tsx 是 Claude Code 二进制的第一段 TypeScript:在加载 main.tsx 之前处理 --version、daemon worker、bridge、bg sessions、MCP sidecar 等 fast-path。init.ts 则是 memoize 的全局初始化(配置、telemetry、网络 agent)。本章要求你能画出「用户执行 claude → 哪条分支 → 是否调用 init()」的决策树。
学完本章你应该能
- 解释为何 ABLATION_BASELINE 必须在 cli.tsx 而非 init.ts
- 列举至少 5 条 cli fast-path 及其 dynamic import 目标
- 描述 init.ts 中 enableConfigs → safe env → telemetry 延迟加载顺序
- 理解 startCapturingEarlyInput 与 main import 的时序
- 知道 init 失败时 showInvalidConfigDialog 的动态 import 策略
核心概念(先读懂这些)
cli.tsx 设计目标:最小化冷启动模块评估
--version 路径零 dynamic import(除 MACRO.VERSION 内联)。其他路径先 import startupProfiler,再按 argv 分支 lazy load。BashTool/AgentTool 在 module eval 时捕获 DISABLE_BACKGROUND_TASKS,所以 ablation env 必须在 cli 顶层设置,init() 太晚。
init 与 cli 的职责分离
cli 只做 argv 路由;init 做一次性全局副作用(configs、graceful shutdown、OAuth、GrowthBook 相关 promise)。Agent SDK 测试可 mock init 而不经过 cli。init 使用 memoize 保证重复调用 no-op。
建议学习步骤
- 阅读 cli 顶层 env 与 ablation 块(源码块 A)
- 阅读 --version 与 cli_entry checkpoint(源码块 B)
- 阅读 bridge/daemon/bg 代表 fast-path(源码块 C)
- 阅读 fallthrough 到 main.tsx(源码块 D)
- 阅读 init.ts enableConfigs 与延迟 telemetry(源码块 E、F)
常见误区
注意
feature() 必须 inline 在 if 条件里,否则 DCE 无法剔除 ant-only 代码
注意
templates 路径用 process.exit(0) 而非 return——Ink TUI 可能残留 event loop handle
注意
init 里 React 的 showInvalidConfigDialog 仅在 ConfigParseError 路径 dynamic import
cli.tsx 在进程中的位置
npm bin 字段指向编译后的 cli.js,其实质 re-export cli.tsx::main()。
cli.tsx main()
│
├─ [0 imports path] --version / -v / -V
│
├─ profileCheckpoint('cli_entry')
│
├─ ant-only: --dump-system-prompt
├─ sidecar: --claude-in-chrome-mcp, --chrome-native-host, --computer-use-mcp
├─ internal: --daemon-worker
├─ bridge: remote-control / rc / remote / sync / bridge
├─ daemon subcommand
├─ bg: ps / logs / attach / kill / --bg
├─ templates: new / list / reply
├─ environment-runner / self-hosted-runner
├─ tmux+worktree fast-path
├─ --update redirect → argv rewrite
├─ --bare → CLAUDE_CODE_SIMPLE=1
│
└─ startCapturingEarlyInput()
→ import('../main.js') → cliMain()
main.tsx 内再调用 init()、解析 Commander、最终 launchRepl 或 headless query。
顶层 side effects:env 与 ablation
cli 文件顶部 故意 保留 top-level side effects(eslint 豁免):
COREPACK_ENABLE_AUTO_PIN = '0'— 防止 corepack 污染 package.jsonCLAUDE_CODE_REMOTE时追加NODE_OPTIONS --max-old-space-size=8192ABLATION_BASELINEfeature + env 时批量设置 SIMPLE、DISABLE_THINKING、DISABLE_COMPACT 等
第三点注释强调:BashTool/AgentTool/PowerShellTool 在 import 时 读取 env 到 module-level const,init() 运行太晚。
CCR 容器 16GB 内存场景通过 REMOTE 分支限制 heap,避免 OOM。
源码引用: src/entrypoints/cli.tsx · 第 1–26 行(共 308 行)
1| import { feature } from 'bun:bundle'
2|
3| // Bugfix for corepack auto-pinning, which adds yarnpkg to peoples' package.jsons
4| // eslint-disable-next-line custom-rules/no-top-level-side-effects
5| process.env.COREPACK_ENABLE_AUTO_PIN = '0'
6|
7| // Set max heap size for child processes in CCR environments (containers have 16GB)
8| // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level, custom-rules/safe-env-boolean-check
9| if (process.env.CLAUDE_CODE_REMOTE === 'true') {
10| // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level
11| const existing = process.env.NODE_OPTIONS || ''
12| // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level
13| process.env.NODE_OPTIONS = existing
14| ? `${existing} --max-old-space-size=8192`
15| : '--max-old-space-size=8192'
16| }
17|
18| // Harness-science L0 ablation baseline. Inlined here (not init.ts) because
19| // BashTool/AgentTool/PowerShellTool capture DISABLE_BACKGROUND_TASKS into
20| // module-level consts at import time — init() runs too late. feature() gate
21| // DCEs this entire block from external builds.
22| // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level
23| if (feature('ABLATION_BASELINE') && process.env.CLAUDE_CODE_ABLATION_BASELINE) {
24| for (const k of [
25| 'CLAUDE_CODE_SIMPLE',
26| 'CLAUDE_CODE_DISABLE_THINKING',
源码引用: src/entrypoints/cli.tsx · 第 16–26 行(共 308 行)
16| }
17|
18| // Harness-science L0 ablation baseline. Inlined here (not init.ts) because
19| // BashTool/AgentTool/PowerShellTool capture DISABLE_BACKGROUND_TASKS into
20| // module-level consts at import time — init() runs too late. feature() gate
21| // DCEs this entire block from external builds.
22| // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level
23| if (feature('ABLATION_BASELINE') && process.env.CLAUDE_CODE_ABLATION_BASELINE) {
24| for (const k of [
25| 'CLAUDE_CODE_SIMPLE',
26| 'CLAUDE_CODE_DISABLE_THINKING',
--version 零加载快路径
args.length === 1 && (--version|-v|-V) 时:
- 直接
console.log(${MACRO.VERSION} (Claude Code)) - return,不 import startupProfiler、不 touch main
这是 packaging 与 CI 探测版本的最热路径,必须保持无依赖。MACRO.VERSION 由 Bun bundle 内联。
对比:下一条路径才 await import('../utils/startupProfiler.js') 并 profileCheckpoint('cli_entry')。
源码引用: src/entrypoints/cli.tsx · 第 28–48 行(共 308 行)
28| 'DISABLE_COMPACT',
29| 'DISABLE_AUTO_COMPACT',
30| 'CLAUDE_CODE_DISABLE_AUTO_MEMORY',
31| 'CLAUDE_CODE_DISABLE_BACKGROUND_TASKS',
32| ]) {
33| // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level
34| process.env[k] ??= '1'
35| }
36| }
37|
38| /**
39| * Bootstrap entrypoint - checks for special flags before loading the full CLI.
40| * All imports are dynamic to minimize module evaluation for fast paths.
41| * Fast-path for --version has zero imports beyond this file.
42| */
43| async function main(): Promise<void> {
44| const args = process.argv.slice(2)
45|
46| // Fast-path for --version/-v: zero module loading needed
47| if (
48| args.length === 1 &&
源码引用: src/entrypoints/cli.tsx · 第 33–42 行(共 308 行)
33| // eslint-disable-next-line custom-rules/no-top-level-side-effects, custom-rules/no-process-env-top-level
34| process.env[k] ??= '1'
35| }
36| }
37|
38| /**
39| * Bootstrap entrypoint - checks for special flags before loading the full CLI.
40| * All imports are dynamic to minimize module evaluation for fast paths.
41| * Fast-path for --version has zero imports beyond this file.
42| */
代表 fast-path:bridge 与 bg
bridge 路径(feature BRIDGE_MODE):
- enableConfigs
- OAuth accessToken 检查 先于 GrowthBook gate(无 auth 则 GB 无 user context)
- waitForPolicyLimitsToLoad + allow_remote_control
- bridgeMain(args.slice(1))
bg 路径(feature BG_SESSIONS):
- ps / logs / attach / kill / --bg / --background
- enableConfigs → dynamic import cli/bg.js
- switch 分发 handler
两者均 不 import main.tsx,避免 Ink/React 进入内存。
源码引用: src/entrypoints/cli.tsx · 第 108–162 行(共 308 行)
108| // it calls them inside its run() fn.
109| if (feature('DAEMON') && args[0] === '--daemon-worker') {
110| const { runDaemonWorker } = await import('../daemon/workerRegistry.js')
111| await runDaemonWorker(args[1])
112| return
113| }
114|
115| // Fast-path for `claude remote-control` (also accepts legacy `claude remote` / `claude sync` / `claude bridge`):
116| // serve local machine as bridge environment.
117| // feature() must stay inline for build-time dead code elimination;
118| // isBridgeEnabled() checks the runtime GrowthBook gate.
119| if (
120| feature('BRIDGE_MODE') &&
121| (args[0] === 'remote-control' ||
122| args[0] === 'rc' ||
123| args[0] === 'remote' ||
124| args[0] === 'sync' ||
125| args[0] === 'bridge')
126| ) {
127| profileCheckpoint('cli_bridge_path')
128| const { enableConfigs } = await import('../utils/config.js')
129| enableConfigs()
130|
131| const { getBridgeDisabledReason, checkBridgeMinVersion } = await import(
132| '../bridge/bridgeEnabled.js'
133| )
134| const { BRIDGE_LOGIN_ERROR } = await import('../bridge/types.js')
135| const { bridgeMain } = await import('../bridge/bridgeMain.js')
136| const { exitWithError } = await import('../utils/process.js')
137|
138| // Auth check must come before the GrowthBook gate check — without auth,
139| // GrowthBook has no user context and would return a stale/default false.
140| // getBridgeDisabledReason awaits GB init, so the returned value is fresh
141| // (not the stale disk cache), but init still needs auth headers to work.
142| const { getClaudeAIOAuthTokens } = await import('../utils/auth.js')
143| if (!getClaudeAIOAuthTokens()?.accessToken) {
144| exitWithError(BRIDGE_LOGIN_ERROR)
145| }
146| const disabledReason = await getBridgeDisabledReason()
147| if (disabledReason) {
148| exitWithError(`Error: ${disabledReason}`)
149| }
150| const versionError = checkBridgeMinVersion()
151| if (versionError) {
152| exitWithError(versionError)
153| }
154|
155| // Bridge is a remote control feature - check policy limits
156| const { waitForPolicyLimitsToLoad, isPolicyAllowed } = await import(
157| '../services/policyLimits/index.js'
158| )
159| await waitForPolicyLimitsToLoad()
160| if (!isPolicyAllowed('allow_remote_control')) {
161| exitWithError(
162| "Error: Remote Control is disabled by your organization's policy.",
源码引用: src/entrypoints/cli.tsx · 第 182–209 行(共 308 行)
182| // Fast-path for `claude ps|logs|attach|kill` and `--bg`/`--background`.
183| // Session management against the ~/.claude/sessions/ registry. Flag
184| // literals are inlined so bg.js only loads when actually dispatching.
185| if (
186| feature('BG_SESSIONS') &&
187| (args[0] === 'ps' ||
188| args[0] === 'logs' ||
189| args[0] === 'attach' ||
190| args[0] === 'kill' ||
191| args.includes('--bg') ||
192| args.includes('--background'))
193| ) {
194| profileCheckpoint('cli_bg_path')
195| const { enableConfigs } = await import('../utils/config.js')
196| enableConfigs()
197| const bg = await import('../cli/bg.js')
198| switch (args[0]) {
199| case 'ps':
200| await bg.psHandler(args.slice(1))
201| break
202| case 'logs':
203| await bg.logsHandler(args[1])
204| break
205| case 'attach':
206| await bg.attachHandler(args[1])
207| break
208| case 'kill':
209| await bg.killHandler(args[1])
Fallthrough:main.tsx 与 early input
无 fast-path 匹配时:
startCapturingEarlyInput()
profileCheckpoint('cli_before_main_import')
const { main: cliMain } = await import('../main.js')
profileCheckpoint('cli_after_main_import')
await cliMain()
profileCheckpoint('cli_after_main_complete')
startCapturingEarlyInput 在用户敲键与 main 模块解析并行时缓冲 stdin,减少 REPL 首屏丢键。
--bare 在 fallthrough 前设置 CLAUDE_CODE_SIMPLE=1,使 Commander 构建选项时即生效,而非仅在 action handler 内。
void main() 顶层启动 async main,eslint 允许 top-level side effect。
源码引用: src/entrypoints/cli.tsx · 第 276–302 行(共 308 行)
276| const { exitWithError } = await import('../utils/process.js')
277| exitWithError(result.error)
278| }
279| }
280| }
281|
282| // Redirect common update flag mistakes to the update subcommand
283| if (
284| args.length === 1 &&
285| (args[0] === '--update' || args[0] === '--upgrade')
286| ) {
287| process.argv = [process.argv[0]!, process.argv[1]!, 'update']
288| }
289|
290| // --bare: set SIMPLE early so gates fire during module eval / commander
291| // option building (not just inside the action handler).
292| if (args.includes('--bare')) {
293| process.env.CLAUDE_CODE_SIMPLE = '1'
294| }
295|
296| // No special flags detected, load and run the full CLI
297| const { startCapturingEarlyInput } = await import('../utils/earlyInput.js')
298| startCapturingEarlyInput()
299| profileCheckpoint('cli_before_main_import')
300| const { main: cliMain } = await import('../main.js')
301| profileCheckpoint('cli_after_main_import')
302| await cliMain()
源码引用: src/entrypoints/cli.tsx · 第 281–285 行(共 308 行)
281|
282| // Redirect common update flag mistakes to the update subcommand
283| if (
284| args.length === 1 &&
285| (args[0] === '--update' || args[0] === '--upgrade')
init.ts:memoize 与 configs
export const init = memoize(async (): Promise<void> => { ... })
启动序列要点:
enableConfigs()— 校验 settings,启用配置系统applySafeConfigEnvironmentVariables()— 信任对话框前仅应用安全 envapplyExtraCACertsFromConfig()— TLS 握手前注入 CAsetupGracefulShutdown()- 延迟 dynamic import firstPartyEventLogger + growthbook
populateOAuthAccountInfoIfNeeded()- remote managed settings / policy limits promise 初始化
configureGlobalMTLS()/configureGlobalAgents()/preconnectAnthropicApi()
注释说明 telemetry OpenTelemetry ~400KB 延迟到 setMeterState,gRPC exporter ~700KB 更晚。
源码引用: src/entrypoints/init.ts · 第 57–84 行(共 341 行)
57| export const init = memoize(async (): Promise<void> => {
58| const initStartTime = Date.now()
59| logForDiagnosticsNoPII('info', 'init_started')
60| profileCheckpoint('init_function_start')
61|
62| // Validate configs are valid and enable configuration system
63| try {
64| const configsStart = Date.now()
65| enableConfigs()
66| logForDiagnosticsNoPII('info', 'init_configs_enabled', {
67| duration_ms: Date.now() - configsStart,
68| })
69| profileCheckpoint('init_configs_enabled')
70|
71| // Apply only safe environment variables before trust dialog
72| // Full environment variables are applied after trust is established
73| const envVarsStart = Date.now()
74| applySafeConfigEnvironmentVariables()
75|
76| // Apply NODE_EXTRA_CA_CERTS from settings.json to process.env early,
77| // before any TLS connections. Bun caches the TLS cert store at boot
78| // via BoringSSL, so this must happen before the first TLS handshake.
79| applyExtraCACertsFromConfig()
80|
81| logForDiagnosticsNoPII('info', 'init_safe_env_vars_applied', {
82| duration_ms: Date.now() - envVarsStart,
83| })
84| profileCheckpoint('init_safe_env_vars_applied')
源码引用: src/entrypoints/init.ts · 第 86–106 行(共 341 行)
86| // Make sure things get flushed on exit
87| setupGracefulShutdown()
88| profileCheckpoint('init_after_graceful_shutdown')
89|
90| // Initialize 1P event logging (no security concerns, but deferred to avoid
91| // loading OpenTelemetry sdk-logs at startup). growthbook.js is already in
92| // the module cache by this point (firstPartyEventLogger imports it), so the
93| // second dynamic import adds no load cost.
94| void Promise.all([
95| import('../services/analytics/firstPartyEventLogger.js'),
96| import('../services/analytics/growthbook.js'),
97| ]).then(([fp, gb]) => {
98| fp.initialize1PEventLogging()
99| // Rebuild the logger provider if tengu_1p_event_batch_config changes
100| // mid-session. Change detection (isEqual) is inside the handler so
101| // unchanged refreshes are no-ops.
102| gb.onGrowthBookRefresh(() => {
103| void fp.reinitialize1PEventLoggingIfConfigChanged()
104| })
105| })
106| profileCheckpoint('init_after_1p_event_logging')
源码引用: src/entrypoints/init.ts · 第 120–159 行(共 341 行)
120| // Initialize the loading promise early so that other systems (like plugin hooks)
121| // can await remote settings loading. The promise includes a timeout to prevent
122| // deadlocks if loadRemoteManagedSettings() is never called (e.g., Agent SDK tests).
123| if (isEligibleForRemoteManagedSettings()) {
124| initializeRemoteManagedSettingsLoadingPromise()
125| }
126| if (isPolicyLimitsEligible()) {
127| initializePolicyLimitsLoadingPromise()
128| }
129| profileCheckpoint('init_after_remote_settings_check')
130|
131| // Record the first start time
132| recordFirstStartTime()
133|
134| // Configure global mTLS settings
135| const mtlsStart = Date.now()
136| logForDebugging('[init] configureGlobalMTLS starting')
137| configureGlobalMTLS()
138| logForDiagnosticsNoPII('info', 'init_mtls_configured', {
139| duration_ms: Date.now() - mtlsStart,
140| })
141| logForDebugging('[init] configureGlobalMTLS complete')
142|
143| // Configure global HTTP agents (proxy and/or mTLS)
144| const proxyStart = Date.now()
145| logForDebugging('[init] configureGlobalAgents starting')
146| configureGlobalAgents()
147| logForDiagnosticsNoPII('info', 'init_proxy_configured', {
148| duration_ms: Date.now() - proxyStart,
149| })
150| logForDebugging('[init] configureGlobalAgents complete')
151| profileCheckpoint('init_network_configured')
152|
153| // Preconnect to the Anthropic API — overlap TCP+TLS handshake
154| // (~100-200ms) with the ~100ms of action-handler work before the API
155| // request. After CA certs + proxy agents are configured so the warmed
156| // connection uses the right transport. Fire-and-forget; skipped for
157| // proxy/mTLS/unix/cloud-provider where the SDK's dispatcher wouldn't
158| // reuse the global pool.
159| preconnectAnthropicApi()
init 与 cli 的衔接点
main.tsx 在交互路径 await init() 后 launchRepl;headless query 同样依赖 init。
daemon worker fast-path 不 enableConfigs——worker run() 内按需调用。
startupProfiler checkpoint 序列:cli_entry → cli_before_main_import → init_configs_enabled → cli_after_main_complete。
earlyInput 与 init 并行:用户按键在 main import 完成前已缓冲。
源码引用: src/entrypoints/init.ts · 第 1–50 行(共 341 行)
1| import { profileCheckpoint } from '../utils/startupProfiler.js'
2| import '../bootstrap/state.js'
3| import '../utils/config.js'
4| import type { Attributes, MetricOptions } from '@opentelemetry/api'
5| import memoize from 'lodash-es/memoize.js'
6| import { getIsNonInteractiveSession } from 'src/bootstrap/state.js'
7| import type { AttributedCounter } from '../bootstrap/state.js'
8| import { getSessionCounter, setMeter } from '../bootstrap/state.js'
9| import { shutdownLspServerManager } from '../services/lsp/manager.js'
10| import { populateOAuthAccountInfoIfNeeded } from '../services/oauth/client.js'
11| import {
12| initializePolicyLimitsLoadingPromise,
13| isPolicyLimitsEligible,
14| } from '../services/policyLimits/index.js'
15| import {
16| initializeRemoteManagedSettingsLoadingPromise,
17| isEligibleForRemoteManagedSettings,
18| waitForRemoteManagedSettingsToLoad,
19| } from '../services/remoteManagedSettings/index.js'
20| import { preconnectAnthropicApi } from '../utils/apiPreconnect.js'
21| import { applyExtraCACertsFromConfig } from '../utils/caCertsConfig.js'
22| import { registerCleanup } from '../utils/cleanupRegistry.js'
23| import { enableConfigs, recordFirstStartTime } from '../utils/config.js'
24| import { logForDebugging } from '../utils/debug.js'
25| import { detectCurrentRepository } from '../utils/detectRepository.js'
26| import { logForDiagnosticsNoPII } from '../utils/diagLogs.js'
27| import { initJetBrainsDetection } from '../utils/envDynamic.js'
28| import { isEnvTruthy } from '../utils/envUtils.js'
29| import { ConfigParseError, errorMessage } from '../utils/errors.js'
30| // showInvalidConfigDialog is dynamically imported in the error path to avoid loading React at init
31| import {
32| gracefulShutdownSync,
33| setupGracefulShutdown,
34| } from '../utils/gracefulShutdown.js'
35| import {
36| applyConfigEnvironmentVariables,
37| applySafeConfigEnvironmentVariables,
38| } from '../utils/managedEnv.js'
39| import { configureGlobalMTLS } from '../utils/mtls.js'
40| import {
41| ensureScratchpadDir,
42| isScratchpadEnabled,
43| } from '../utils/permissions/filesystem.js'
44| // initializeTelemetry is loaded lazily via import() in setMeterState() to defer
45| // ~400KB of OpenTelemetry + protobuf modules until telemetry is actually initialized.
46| // gRPC exporters (~700KB via @grpc/grpc-js) are further lazy-loaded within instrumentation.ts.
47| import { configureGlobalAgents } from '../utils/proxy.js'
48| import { isBetaTracingEnabled } from '../utils/telemetry/betaSessionTracing.js'
49| import { getTelemetryAttributes } from '../utils/telemetryAttributes.js'
50| import { setShellIfWindows } from '../utils/windowsPaths.js'
源码引用: src/entrypoints/cli.tsx · 第 95–106 行(共 308 行)
95| ) {
96| profileCheckpoint('cli_computer_use_mcp_path')
97| const { runComputerUseMcpServer } = await import(
98| '../utils/computerUse/mcpServer.js'
99| )
100| await runComputerUseMcpServer()
101| return
102| }
103|
104| // Fast-path for `--daemon-worker=<kind>` (internal — supervisor spawns this).
105| // Must come before the daemon subcommand check: spawned per-worker, so
106| // perf-sensitive. No enableConfigs(), no analytics sinks at this layer —
daemon / templates / environment-runner 快路径
除 bridge/bg 外,cli 还有多条 headless fast-path:
claude daemon [sub]→ daemonMain,enableConfigs + initSinksclaude new|list|reply→ templatesMain,process.exit(0) 强制退出(Ink fleet TUI 残留 handle)claude environment-runner/self-hosted-runner→ 各自 main
共同模式:profileCheckpoint 标记分支 → dynamic import 专用 main → return,不加载 main.tsx。
--bare 在 fallthrough 前设置 SIMPLE,影响 Commander 构建时可见的选项集,与 init 内 enableConfigs 互补。
源码引用: src/entrypoints/cli.tsx · 第 164–180 行(共 308 行)
164| }
165|
166| await bridgeMain(args.slice(1))
167| return
168| }
169|
170| // Fast-path for `claude daemon [subcommand]`: long-running supervisor.
171| if (feature('DAEMON') && args[0] === 'daemon') {
172| profileCheckpoint('cli_daemon_path')
173| const { enableConfigs } = await import('../utils/config.js')
174| enableConfigs()
175| const { initSinks } = await import('../utils/sinks.js')
176| initSinks()
177| const { daemonMain } = await import('../daemon/main.js')
178| await daemonMain(args.slice(1))
179| return
180| }
源码引用: src/entrypoints/cli.tsx · 第 211–222 行(共 308 行)
211| default:
212| await bg.handleBgFlag(args)
213| }
214| return
215| }
216|
217| // Fast-path for template job commands.
218| if (
219| feature('TEMPLATES') &&
220| (args[0] === 'new' || args[0] === 'list' || args[0] === 'reply')
221| ) {
222| profileCheckpoint('cli_templates_path')
init 错误路径与 scratchpad
init.ts 后半(未在子章节全文展开)还包含:
- ConfigParseError → dynamic import showInvalidConfigDialog(避免 init 路径加载 React)
- ensureScratchpadDir — isScratchpadEnabled 时创建 scratchpad
- shutdownLspServerManager — gracefulShutdown 注册
- waitForRemoteManagedSettingsToLoad — SDK 测试防 deadlock timeout
init 使用 memoize:测试与重复入口可安全多次 await init()。
setMeterState 延迟 initializeTelemetry,OpenTelemetry protobuf 仅在需要 meter 时加载。
源码引用: src/entrypoints/init.ts · 第 160–200 行(共 341 行)
160|
161| // CCR upstreamproxy: start the local CONNECT relay so agent subprocesses
162| // can reach org-configured upstreams with credential injection. Gated on
163| // CLAUDE_CODE_REMOTE + GrowthBook; fail-open on any error. Lazy import so
164| // non-CCR startups don't pay the module load. The getUpstreamProxyEnv
165| // function is registered with subprocessEnv.ts so subprocess spawning can
166| // inject proxy vars without a static import of the upstreamproxy module.
167| if (isEnvTruthy(process.env.CLAUDE_CODE_REMOTE)) {
168| try {
169| const { initUpstreamProxy, getUpstreamProxyEnv } = await import(
170| '../upstreamproxy/upstreamproxy.js'
171| )
172| const { registerUpstreamProxyEnvFn } = await import(
173| '../utils/subprocessEnv.js'
174| )
175| registerUpstreamProxyEnvFn(getUpstreamProxyEnv)
176| await initUpstreamProxy()
177| } catch (err) {
178| logForDebugging(
179| `[init] upstreamproxy init failed: ${err instanceof Error ? err.message : String(err)}; continuing without proxy`,
180| { level: 'warn' },
181| )
182| }
183| }
184|
185| // Set up git-bash if relevant
186| setShellIfWindows()
187|
188| // Register LSP manager cleanup (initialization happens in main.tsx after --plugin-dir is processed)
189| registerCleanup(shutdownLspServerManager)
190|
191| // gh-32730: teams created by subagents (or main agent without
192| // explicit TeamDelete) were left on disk forever. Register cleanup
193| // for all teams created this session. Lazy import: swarm code is
194| // behind feature gate and most sessions never create teams.
195| registerCleanup(async () => {
196| const { cleanupSessionTeams } = await import(
197| '../utils/swarm/teamHelpers.js'
198| )
199| await cleanupSessionTeams()
200| })
本章小结与延伸
cli = argv 路由器 + 冷启动优化;init = 全局一次性初始化。全量交互 CLI 在 main.tsx。 继续学习: