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') from src/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:

  1. Reads API keys and provider selections from disk
  2. Caches frequently accessed settings in memory
  3. 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:

  1. Provider resolution via src/utils/providerProfiles.ts determines which model handles the request
  2. Token budgeting from src/query/tokenBudget.ts enforces usage limits
  3. 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

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →