OpenClaude CLI Startup Sequence: From Shell Command to Interactive REPL
The OpenClaude CLI initialization flow progresses through the bin/openclaude wrapper, the src/commands.ts orchestrator, and finally mounts the React-Ink application via src/main.tsx before rendering the interactive REPL.
The startup sequence for the OpenClaude CLI—that ships out of the Gitlawb/openclaude repository—follows a carefully staged boot process. Understanding this flow helps developers debug initialization issues, optimize cold-start performance, and extend the tool's functionality.
Entry Point: The Binary Wrapper
The OpenClaude CLI begins execution at bin/openclaude, a thin Node.js wrapper responsible for launching the TypeScript application.
#!/usr/bin/env node
import('../src/main.tsx').catch(err => {
console.error('OpenClaude failed to start:', err);
process.exit(1);
});
This pattern—dynamic import with error handling—ensures graceful failure reporting if the main module fails to load, while keeping the wrapper itself minimal and fast to parse.
Command Orchestration in src/commands.ts
The dynamic import resolves to src/commands.ts, the central orchestration module. This file performs three critical tasks during startup:
- Initialize profiling via
profileCheckpoint('main_tsx_entry')fromsrc/utils/startupProfiler.ts - Parse command-line flags using
@commander-js/extra-typings - Route execution between fast-path commands and full interactive mode
Flag Parsing Implementation
// From src/commands.ts
import { Command } from '@commander-js/extra-typings';
const cli = new Command('openclaude');
cli
.option('--remote', 'Run in headless remote mode')
.option('--name <title>', 'Give the session a custom name')
.option('--file <path>', 'Load conversation from file')
// ... additional options
.action(async (opts) => {
// Fast-path: skip UI for non-interactive commands
if (opts.remote) {
await runHeadlessJob(opts);
return;
}
// Continue to interactive startup
await startInteractiveSession(opts);
});
await cli.parseAsync(process.argv);
The src/cli/handlers/util.tsx module contains the underlying flag definitions and validation logic used by this parser.
Fast-Path Handling for Non-Interactive Commands
Certain subcommands—openclaude doctor, openclaude version, and openclaude --remote—bypass the full UI initialization. This fast-path block (around line 4200 in src/commands.ts) exits early after completing their specific task, avoiding the overhead of React-Ink mounting and background service startup.
Profile Loading and Provider Validation
For interactive sessions, the CLI loads user configuration through src/utils/settings/settingsCache.ts. This stage:
- Reads API keys and provider selections from disk
- Caches frequently accessed settings in memory
- Validates provider configurations via
src/utils/providerValidation.ts
Failed validation here triggers early exit with actionable error messages, preventing the UI from launching into a broken state.
Mounting the React-Ink Application: src/main.tsx
With configuration validated, control passes to src/main.tsx, the React-Ink entry point:
// src/main.tsx
import React from 'react';
import { render } from 'ink';
import { App } from './components/App';
import { profileCheckpoint } from './utils/startupProfiler';
profileCheckpoint('main_tsx_entry'); // Mark UI boot start
render(<App />); // Mount interactive interface
The profileCheckpoint call records timing data to src/utils/startupProfiler.ts, which aggregates metrics and eventually reports tengu_startup_perf events to Statsig for performance monitoring.
Background Service Initialization
While the UI renders, several background services initialize asynchronously:
| Service | Source File | Purpose |
|---|---|---|
| MCP Connector | src/services/mcpServerApproval.tsx |
Manages Model-Control-Plane server connections |
| Error Reporting | src/utils/sentry.ts |
Lazy-loaded Sentry integration for crash tracking |
| Session Management | src/utils/sessionStart.ts |
Initializes conversation state and history |
The Sentry integration specifically uses lazy loading to avoid blocking the critical path—errors during early startup are queued and reported once the client initializes.
REPL Rendering and Query Engine Binding
The root App component renders src/screens/REPL.tsx, which implements the core interactive loop:
// src/screens/REPL.tsx
import { useAppState } from '../hooks/useAppState';
import { QueryEngine } from '../QueryEngine';
export const REPL = () => {
const { state, dispatch } = useAppState();
const handleUserMessage = async (input: string) => {
dispatch({ type: 'ADD_USER_MESSAGE', payload: input });
const result = await QueryEngine.run(input, {
provider: state.activeProvider,
tokenBudget: getTokenBudget(state),
// ... additional context
});
dispatch({ type: 'ADD_ASSISTANT_MESSAGE', payload: result });
};
return (
<Box flexDirection="column">
<ChatHistory messages={state.messages} />
<InputPrompt onSubmit={handleUserMessage} />
</Box>
);
};
The Complete Query Lifecycle
Each user message triggers the QueryEngine execution flow defined in src/QueryEngine.ts:
- Provider resolution via
src/utils/providerProfiles.tsdetermines which model handles the request - Token budgeting from
src/query/tokenBudget.tsenforces usage limits - Streaming response pipes through the Ink component tree in real-time
Startup Profiling and Telemetry
The src/utils/startupProfiler.ts module tracks timing across all initialization phases:
// Conceptual structure from startupProfiler.ts
export function profileCheckpoint(phase: string): void {
checkpoints[phase] = performance.now();
}
export function reportStartupMetrics(): void {
const duration = checkpoints['main_tsx_entry'] - checkpoints['commands_ts_entry'];
statsig.logEvent({ eventName: 'tengu_startup_perf', value: duration });
}
This data enables the maintainers to identify regression in cold-start performance across releases.
Summary
bin/openclaudelaunches the Node process with error handlingsrc/commands.tsparses flags, profiles startup, and routes to fast-path or interactive flowsrc/utils/settings/settingsCache.tsloads and validates user configurationsrc/main.tsxmounts the React-Ink application rootsrc/services/mcpServerApproval.tsx,src/utils/sentry.ts, andsrc/utils/sessionStart.tsinitialize background servicessrc/screens/REPL.tsxrenders the interactive interface bound tosrc/QueryEngine.tssrc/utils/startupProfiler.tsrecords and reports performance metrics to Statsig
Frequently Asked Questions
What file actually parses the OpenClaude CLI arguments?
The src/commands.ts module contains the argument parsing logic, delegating to @commander-js/extra-typings for type-safe flag definitions. The underlying handler utilities reside in src/cli/handlers/util.tsx.
How does OpenClaude avoid slow startup for simple commands?
A fast-path block in src/commands.ts (approximately line 4200) detects non-interactive commands like --remote, doctor, or version and exits immediately after completion—skipping React-Ink mounting, profile loading, and background service initialization.
Where is startup performance measured?
The src/utils/startupProfiler.ts module records phase timings starting from profileCheckpoint('main_tsx_entry') and reports aggregated metrics as Statsig tengu_startup_perf events. Checkpoints are placed at module boundaries including commands.ts entry and main.tsx mount.
What determines which AI provider handles a query?
src/utils/providerProfiles.ts resolves the active provider at session startup based on user settings from src/utils/settings/settingsCache.ts. The QueryEngine consults this resolution for each request, enabling per-message provider overrides through explicit flags.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →