Skip to content

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.

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 mode

Stage 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:

src/entrypoints/cli.tsx
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/SubcommandBehaviorImports
--versionPrint version, exitZero
--dump-system-promptOutput system prompt (ant-only)Minimal — configs + model
--claude-in-chrome-mcpMCP server for Chrome extensionChrome MCP module
--daemon-workerDaemon worker process (ant-only)Worker registry only
remote-controlBridge mode for remote sessionsBridge modules

The key insight: every import is await import() (dynamic). Nothing loads until needed.

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) containers
if (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:

src/entrypoints/init.ts
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()
// ...
})

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 config
  • applySafeConfigEnvironmentVariables() 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() before configureGlobalAgents() — mTLS settings feed into proxy agents

After the user accepts the trust dialog, additional sensitive config is applied:

// Called later in the startup sequence, after trust is established
export async function initializeTelemetryAfterTrust(): Promise<void> {
// Now safe to apply ALL environment variables (including sensitive ones)
applyConfigEnvironmentVariables()
// Initialize telemetry, analytics, etc.
}

The main module is the largest file in the codebase. It registers Commander.js commands, parses arguments, and launches the appropriate mode.

main.tsx fires three parallel operations before any imports finish evaluating:

// src/main.tsx — Lines 1-20
import { 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 parallel

This parallel prefetch pattern saves ~65ms on macOS by overlapping keychain reads with the remaining ~135ms of module imports.

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 options

After argument parsing, the startup proceeds:

  1. Config validation — Check for invalid settings, show dialog if needed
  2. Auth flow — Validate API key or OAuth tokens
  3. GrowthBook init — Feature flags from Anthropic’s experimentation platform
  4. MCP server startup — Connect to configured MCP servers in parallel
  5. Tool assembly — Build the tool pool based on permissions and config
  6. Agent definitions — Load agent definitions from .claude/agents/
  7. Mode dispatch — Launch REPL (interactive) or print mode (headless)

The setup() function handles post-init environment configuration:

src/setup.ts
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> {
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"]

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 available
gracefulShutdownSync()
// Restores terminal, flushes sync-safe writes

When --worktree is passed, setup.ts creates an isolated git worktree:

// src/setup.ts — Worktree handling
if (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())
}

For --dangerously-skip-permissions, setup.ts enforces safety:

// src/setup.ts — Security validation
if (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)
}
}
}
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 --> [*]

The bootstrap sequence includes several performance-critical optimizations:

  1. Parallel prefetch — MDM reads, keychain reads, and API preconnection run in parallel with imports
  2. Dynamic imports — Fast paths load zero modules; main path uses await import()
  3. Memoized initinit() is memoize()d to prevent double initialization
  4. Background jobs — Non-critical work (attribution hooks, session file access) runs via void import().then()
  5. Startup profilerprofileCheckpoint() calls throughout allow measuring each stage
// Profiling checkpoints in the startup path
profileCheckpoint('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')