Entry & Bootstrap
Understanding how Claude Code bootstraps is essential for understanding everything else. The startup sequence spans four files, each with a distinct role: fast-path routing, core initialization, CLI framework, and environment setup.
Bootstrap Sequence Overview
Section titled “Bootstrap Sequence Overview”sequenceDiagram participant OS as Operating System participant CLI as cli.tsx participant INIT as init.ts participant MAIN as main.tsx participant SETUP as setup.ts participant REPL as REPL.tsx
OS->>CLI: bun run cli.tsx CLI->>CLI: Fast-path checks (--version, --bridge, etc.) CLI->>INIT: init() INIT->>INIT: enableConfigs() INIT->>INIT: applySafeConfigEnvironmentVariables() INIT->>INIT: setupGracefulShutdown() INIT->>INIT: configureGlobalAgents() (proxy/mTLS) CLI->>MAIN: Dynamic import main.tsx MAIN->>MAIN: Side effects: profiler, MDM, keychain MAIN->>MAIN: Commander.js argument parsing MAIN->>MAIN: API key validation MAIN->>MAIN: MCP server initialization MAIN->>SETUP: setup(cwd, permissionMode, ...) SETUP->>SETUP: setCwd(), captureHooksConfigSnapshot() SETUP->>SETUP: Worktree creation (if --worktree) SETUP->>SETUP: Background jobs, telemetry MAIN->>REPL: launchRepl() or print modeStage 1: Fast-Path Routing (src/entrypoints/cli.tsx)
Section titled “Stage 1: Fast-Path Routing (src/entrypoints/cli.tsx)”The bootstrap entrypoint is designed with zero-import fast paths. Some operations should complete without loading the full application:
async function main(): Promise<void> { const args = process.argv.slice(2)
// Fast-path for --version/-v: zero module loading needed if (args.length === 1 && (args[0] === '--version' || args[0] === '-v' || args[0] === '-V')) { console.log(`${MACRO.VERSION} (Claude Code)`) return }
// For all other paths, load the startup profiler const { profileCheckpoint } = await import('../utils/startupProfiler.js') profileCheckpoint('cli_entry')The fast-path pattern supports several specialized modes:
| Flag/Subcommand | Behavior | Imports |
|---|---|---|
--version | Print version, exit | Zero |
--dump-system-prompt | Output system prompt (ant-only) | Minimal — configs + model |
--claude-in-chrome-mcp | MCP server for Chrome extension | Chrome MCP module |
--daemon-worker | Daemon worker process (ant-only) | Worker registry only |
remote-control | Bridge mode for remote sessions | Bridge modules |
The key insight: every import is await import() (dynamic). Nothing loads until needed.
Environment Setup in cli.tsx
Section titled “Environment Setup in cli.tsx”Before the main CLI loads, cli.tsx handles two critical environment adjustments:
// src/entrypoints/cli.tsx — Top-level side effects// Prevent corepack auto-pinning (modifies package.json)process.env.COREPACK_ENABLE_AUTO_PIN = '0'
// Set max heap size for CCR (Claude Code Remote) containersif (process.env.CLAUDE_CODE_REMOTE === 'true') { const existing = process.env.NODE_OPTIONS || '' process.env.NODE_OPTIONS = existing ? `${existing} --max-old-space-size=8192` : '--max-old-space-size=8192'}Stage 2: Core Initialization (src/entrypoints/init.ts)
Section titled “Stage 2: Core Initialization (src/entrypoints/init.ts)”The init() function is memoized — it runs exactly once regardless of how many times it’s called:
export const init = memoize(async (): Promise<void> => { const initStartTime = Date.now() logForDiagnosticsNoPII('info', 'init_started') profileCheckpoint('init_function_start')
// 1. Enable configuration system enableConfigs()
// 2. Apply SAFE environment variables (before trust dialog) applySafeConfigEnvironmentVariables()
// 3. Apply extra CA certificates from settings applyExtraCACertsFromConfig()
// 4. Register graceful shutdown handlers setupGracefulShutdown()
// 5. Configure mTLS configureGlobalMTLS()
// 6. Configure proxy (HTTP agents) configureGlobalAgents()
// 7. Pre-connect to Anthropic API preconnectAnthropicApi() // ...})Init Sequence Breakdown
Section titled “Init Sequence Breakdown”The init sequence carefully orders operations to maximize parallelism while respecting dependencies:
graph TB A["enableConfigs()"] --> B["applySafeConfigEnvironmentVariables()"] B --> C["applyExtraCACertsFromConfig()"] C --> D["setupGracefulShutdown()"] D --> E["configureGlobalMTLS()"] E --> F["configureGlobalAgents()"]
B --> G["populateOAuthAccountInfoIfNeeded() (async, void)"] B --> H["initJetBrainsDetection() (async, void)"] B --> I["detectCurrentRepository() (async, void)"] B --> J["initializeRemoteManagedSettingsLoadingPromise()"]
F --> K["preconnectAnthropicApi()"]Critical ordering:
enableConfigs()must run first — everything reads from configapplySafeConfigEnvironmentVariables()applies env vars that are safe pre-trust (no dangerous keys)applyExtraCACertsFromConfig()must happen before any TLS connection (Bun caches cert store at boot)configureGlobalMTLS()beforeconfigureGlobalAgents()— mTLS settings feed into proxy agents
Post-Trust Initialization
Section titled “Post-Trust Initialization”After the user accepts the trust dialog, additional sensitive config is applied:
// Called later in the startup sequence, after trust is establishedexport async function initializeTelemetryAfterTrust(): Promise<void> { // Now safe to apply ALL environment variables (including sensitive ones) applyConfigEnvironmentVariables() // Initialize telemetry, analytics, etc.}Stage 3: CLI Framework (src/main.tsx)
Section titled “Stage 3: CLI Framework (src/main.tsx)”The main module is the largest file in the codebase. It registers Commander.js commands, parses arguments, and launches the appropriate mode.
Side Effects at Import Time
Section titled “Side Effects at Import Time”main.tsx fires three parallel operations before any imports finish evaluating:
// src/main.tsx — Lines 1-20import { profileCheckpoint } from './utils/startupProfiler.js'profileCheckpoint('main_tsx_entry') // Mark entry timestamp
import { startMdmRawRead } from './utils/settings/mdm/rawRead.js'startMdmRawRead() // Fire MDM subprocesses (plutil/reg query) in parallel
import { startKeychainPrefetch } from './utils/secureStorage/keychainPrefetch.js'startKeychainPrefetch() // Fire macOS keychain reads in parallelThis parallel prefetch pattern saves ~65ms on macOS by overlapping keychain reads with the remaining ~135ms of module imports.
Commander.js Registration
Section titled “Commander.js Registration”The CLI uses @commander-js/extra-typings for type-safe argument parsing:
// src/main.tsx (conceptual)import { Command as CommanderCommand, Option } from '@commander-js/extra-typings'
const program = new CommanderCommand() .name('claude') .option('-p, --print', 'Print mode (non-interactive)') .option('--model <model>', 'Model to use') .option('--permission-mode <mode>', 'Permission mode') .option('--dangerously-skip-permissions', 'Skip all permission checks') .option('--resume <session>', 'Resume a previous session') .option('--worktree', 'Create a git worktree for this session') // ... 40+ more optionsStartup Flow in main.tsx
Section titled “Startup Flow in main.tsx”After argument parsing, the startup proceeds:
- Config validation — Check for invalid settings, show dialog if needed
- Auth flow — Validate API key or OAuth tokens
- GrowthBook init — Feature flags from Anthropic’s experimentation platform
- MCP server startup — Connect to configured MCP servers in parallel
- Tool assembly — Build the tool pool based on permissions and config
- Agent definitions — Load agent definitions from
.claude/agents/ - Mode dispatch — Launch REPL (interactive) or print mode (headless)
Stage 4: Environment Setup (src/setup.ts)
Section titled “Stage 4: Environment Setup (src/setup.ts)”The setup() function handles post-init environment configuration:
export async function setup( cwd: string, permissionMode: PermissionMode, allowDangerouslySkipPermissions: boolean, worktreeEnabled: boolean, worktreeName: string | undefined, tmuxEnabled: boolean, customSessionId?: string | null, worktreePRNumber?: number, messagingSocketPath?: string,): Promise<void> {Setup Sequence
Section titled “Setup Sequence”graph TB A["setCwd(cwd)"] --> B["captureHooksConfigSnapshot()"] B --> C["initializeFileChangedWatcher(cwd)"] C --> D{worktreeEnabled?} D -->|Yes| E["createWorktreeForSession()"] E --> F["process.chdir(worktreePath)"] D -->|No| G["Background jobs"] F --> G
G --> H["initSessionMemory()"] G --> I["lockCurrentVersion()"] G --> J["getCommands() prefetch"] G --> K["loadPluginHooks() prefetch"]
K --> L["checkForReleaseNotes()"] L --> M["Permission mode validation"]Signal Handling
Section titled “Signal Handling”Claude Code registers handlers for process termination signals:
// src/utils/gracefulShutdown.ts (called from init.ts)export function setupGracefulShutdown(): void { // Register cleanup for SIGINT, SIGTERM, SIGHUP // Ensures: // - Terminal state is restored (cursor, alternate screen) // - Session data is flushed to disk // - Child processes are killed // - Telemetry is flushed}The gracefulShutdownSync() function provides a synchronous exit path for cases where async cleanup isn’t possible:
// Used in signal handlers where async is not availablegracefulShutdownSync()// Restores terminal, flushes sync-safe writesWorktree Creation
Section titled “Worktree Creation”When --worktree is passed, setup.ts creates an isolated git worktree:
// src/setup.ts — Worktree handlingif (worktreeEnabled) { const hasHook = hasWorktreeCreateHook() const inGit = await getIsGit() if (!hasHook && !inGit) { process.stderr.write(chalk.red( `Error: Can only use --worktree in a git repository` )) process.exit(1) }
const slug = worktreePRNumber ? `pr-${worktreePRNumber}` : (worktreeName ?? getPlanSlug())
worktreeSession = await createWorktreeForSession(getSessionId(), slug, ...)
process.chdir(worktreeSession.worktreePath) setCwd(worktreeSession.worktreePath) setOriginalCwd(getCwd()) setProjectRoot(getCwd())}Permission Mode Validation
Section titled “Permission Mode Validation”For --dangerously-skip-permissions, setup.ts enforces safety:
// src/setup.ts — Security validationif (permissionMode === 'bypassPermissions' || allowDangerouslySkipPermissions) { // Block root/sudo on Unix if (process.platform !== 'win32' && process.getuid?.() === 0 && process.env.IS_SANDBOX !== '1') { console.error('--dangerously-skip-permissions cannot be used with root/sudo') process.exit(1) }
// External users: require Docker/sandbox + no internet if (process.env.USER_TYPE === 'ant') { const [isDocker, hasInternet] = await Promise.all([ envDynamic.getIsDocker(), env.hasInternetAccess(), ]) if (!isSandboxed || hasInternet) { console.error('--dangerously-skip-permissions requires sandbox with no internet') process.exit(1) } }}Process Lifecycle
Section titled “Process Lifecycle”stateDiagram-v2 [*] --> CliEntry: bun run cli.tsx CliEntry --> FastPath: --version, --bridge, etc. FastPath --> [*]: Exit immediately
CliEntry --> Init: Normal path Init --> MainTsx: Dynamic import MainTsx --> ArgParse: Commander.js ArgParse --> AuthCheck: Validate API key AuthCheck --> McpInit: Start MCP servers McpInit --> Setup: setup() Setup --> ReplMode: Interactive Setup --> PrintMode: --print flag
ReplMode --> Running: REPL loop PrintMode --> Running: Single query
Running --> Shutdown: SIGINT/SIGTERM/exit Shutdown --> Cleanup: Flush sessions, restore terminal Cleanup --> [*]Performance Optimizations
Section titled “Performance Optimizations”The bootstrap sequence includes several performance-critical optimizations:
- Parallel prefetch — MDM reads, keychain reads, and API preconnection run in parallel with imports
- Dynamic imports — Fast paths load zero modules; main path uses
await import() - Memoized init —
init()ismemoize()d to prevent double initialization - Background jobs — Non-critical work (attribution hooks, session file access) runs via
void import().then() - Startup profiler —
profileCheckpoint()calls throughout allow measuring each stage
// Profiling checkpoints in the startup pathprofileCheckpoint('cli_entry')profileCheckpoint('init_function_start')profileCheckpoint('init_configs_enabled')profileCheckpoint('init_safe_env_vars_applied')profileCheckpoint('init_after_graceful_shutdown')profileCheckpoint('init_after_1p_event_logging')profileCheckpoint('main_tsx_entry')profileCheckpoint('setup_before_prefetch')profileCheckpoint('setup_after_prefetch')