From d45628c41300b83b466e6a97983099615a50e7d7 Mon Sep 17 00:00:00 2001 From: Rayan Alkhelaiwi Date: Sun, 26 Apr 2026 15:24:44 +0300 Subject: [PATCH] fix(startup): show --model flag override on startup screen (#898) The startup screen was only reading model from env vars and settings, ignoring the --model CLI flag since it's parsed by Commander.js after the banner prints. Now eagerly parses --model from argv before rendering so the displayed model matches what the session will actually use. --- src/components/StartupScreen.test.ts | 69 ++++++++++++++++++++++++++++ src/components/StartupScreen.ts | 16 +++---- src/entrypoints/cli.tsx | 6 ++- 3 files changed, 82 insertions(+), 9 deletions(-) diff --git a/src/components/StartupScreen.test.ts b/src/components/StartupScreen.test.ts index 9b83b108..e26569f1 100644 --- a/src/components/StartupScreen.test.ts +++ b/src/components/StartupScreen.test.ts @@ -14,6 +14,7 @@ const ENV_KEYS = [ 'GEMINI_MODEL', 'MISTRAL_MODEL', 'ANTHROPIC_MODEL', + 'CLAUDE_MODEL', 'NVIDIA_NIM', 'MINIMAX_API_KEY', ] @@ -186,3 +187,71 @@ describe('detectProvider — explicit dedicated-provider env flags', () => { expect(detectProvider().name).toBe('MiniMax') }) }) + +// --- modelOverride from --model flag --- + +describe('detectProvider — modelOverride from --model flag', () => { + test('modelOverride overrides default Anthropic model', () => { + const result = detectProvider('claude-opus-4-6') + expect(result.name).toBe('Anthropic') + expect(result.model).toContain('opus') + }) + + test('modelOverride alias is resolved for Anthropic', () => { + const result = detectProvider('opus') + expect(result.name).toBe('Anthropic') + expect(result.model).toContain('opus') + }) + + test('modelOverride takes priority over ANTHROPIC_MODEL env var', () => { + process.env.ANTHROPIC_MODEL = 'claude-haiku-4-5-20251001' + const result = detectProvider('claude-opus-4-6') + expect(result.name).toBe('Anthropic') + expect(result.model).toContain('opus') + }) + + test('modelOverride takes priority over CLAUDE_MODEL env var', () => { + process.env.CLAUDE_MODEL = 'claude-haiku-4-5-20251001' + const result = detectProvider('claude-opus-4-6') + expect(result.name).toBe('Anthropic') + expect(result.model).toContain('opus') + }) + + test('modelOverride works for OpenAI provider', () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + process.env.OPENAI_API_KEY = 'test-key' + process.env.OPENAI_MODEL = 'gpt-4o' + const result = detectProvider('gpt-4-turbo') + expect(result.model).toContain('gpt-4-turbo') + }) + + test('modelOverride works for Gemini provider', () => { + process.env.CLAUDE_CODE_USE_GEMINI = '1' + const result = detectProvider('gemini-2.5-pro') + expect(result.model).toBe('gemini-2.5-pro') + }) + + test('modelOverride works for Mistral provider', () => { + process.env.CLAUDE_CODE_USE_MISTRAL = '1' + const result = detectProvider('mistral-large-latest') + expect(result.model).toBe('mistral-large-latest') + }) + + test('modelOverride works for GitHub provider', () => { + process.env.CLAUDE_CODE_USE_GITHUB = '1' + const result = detectProvider('gpt-4o') + expect(result.model).toContain('gpt-4o') + }) + + test('undefined modelOverride preserves default behavior', () => { + const result = detectProvider(undefined) + expect(result.name).toBe('Anthropic') + expect(result.model).toContain('sonnet') + }) + + test('no argument preserves default behavior', () => { + const result = detectProvider() + expect(result.name).toBe('Anthropic') + expect(result.model).toContain('sonnet') + }) +}) diff --git a/src/components/StartupScreen.ts b/src/components/StartupScreen.ts index 6014335e..1818a2ef 100644 --- a/src/components/StartupScreen.ts +++ b/src/components/StartupScreen.ts @@ -84,33 +84,33 @@ const LOGO_CLAUDE = [ // ─── Provider detection ─────────────────────────────────────────────────────── -export function detectProvider(): { name: string; model: string; baseUrl: string; isLocal: boolean } { +export function detectProvider(modelOverride?: string): { name: string; model: string; baseUrl: string; isLocal: boolean } { const useGemini = process.env.CLAUDE_CODE_USE_GEMINI === '1' || process.env.CLAUDE_CODE_USE_GEMINI === 'true' const useGithub = process.env.CLAUDE_CODE_USE_GITHUB === '1' || process.env.CLAUDE_CODE_USE_GITHUB === 'true' const useOpenAI = process.env.CLAUDE_CODE_USE_OPENAI === '1' || process.env.CLAUDE_CODE_USE_OPENAI === 'true' const useMistral = process.env.CLAUDE_CODE_USE_MISTRAL === '1' || process.env.CLAUDE_CODE_USE_MISTRAL === 'true' if (useGemini) { - const model = process.env.GEMINI_MODEL || 'gemini-2.0-flash' + const model = modelOverride || process.env.GEMINI_MODEL || 'gemini-2.0-flash' const baseUrl = process.env.GEMINI_BASE_URL || 'https://generativelanguage.googleapis.com/v1beta/openai' return { name: 'Google Gemini', model, baseUrl, isLocal: false } } if (useMistral) { - const model = process.env.MISTRAL_MODEL || 'devstral-latest' + const model = modelOverride || process.env.MISTRAL_MODEL || 'devstral-latest' const baseUrl = process.env.MISTRAL_BASE_URL || 'https://api.mistral.ai/v1' return { name: 'Mistral', model, baseUrl, isLocal: false } } if (useGithub) { - const model = process.env.OPENAI_MODEL || 'github:copilot' + const model = modelOverride || process.env.OPENAI_MODEL || 'github:copilot' const baseUrl = process.env.OPENAI_BASE_URL || 'https://api.githubcopilot.com' return { name: 'GitHub Copilot', model, baseUrl, isLocal: false } } if (useOpenAI) { - const rawModel = process.env.OPENAI_MODEL || 'gpt-4o' + const rawModel = modelOverride || process.env.OPENAI_MODEL || 'gpt-4o' const resolvedRequest = resolveProviderRequest({ model: rawModel, baseUrl: process.env.OPENAI_BASE_URL, @@ -166,7 +166,7 @@ export function detectProvider(): { name: string; model: string; baseUrl: string // Default: Anthropic - check settings.model first, then env vars const settings = getSettings_DEPRECATED() || {} - const modelSetting = settings.model || process.env.ANTHROPIC_MODEL || process.env.CLAUDE_MODEL || 'claude-sonnet-4-6' + const modelSetting = modelOverride || settings.model || process.env.ANTHROPIC_MODEL || process.env.CLAUDE_MODEL || 'claude-sonnet-4-6' const resolvedModel = parseUserSpecifiedModel(modelSetting) const baseUrl = process.env.ANTHROPIC_BASE_URL ?? 'https://api.anthropic.com' const isLocal = isLocalProviderUrl(baseUrl) @@ -182,11 +182,11 @@ function boxRow(content: string, width: number, rawLen: number): string { // ─── Main ───────────────────────────────────────────────────────────────────── -export function printStartupScreen(): void { +export function printStartupScreen(modelOverride?: string): void { // Skip in non-interactive / CI / print mode if (process.env.CI || !process.stdout.isTTY) return - const p = detectProvider() + const p = detectProvider(modelOverride) const W = 62 const out: string[] = [] diff --git a/src/entrypoints/cli.tsx b/src/entrypoints/cli.tsx index 9536c266..ca5f925c 100644 --- a/src/entrypoints/cli.tsx +++ b/src/entrypoints/cli.tsx @@ -134,9 +134,13 @@ async function main(): Promise { await validateProviderEnvForStartupOrExit() + // Parse --model early so the startup screen can display the override + const { eagerParseCliFlag } = await import('../utils/cliArgs.js') + const earlyModelFlag = eagerParseCliFlag('--model') + // Print the gradient startup screen before the Ink UI loads const { printStartupScreen } = await import('../components/StartupScreen.js') - printStartupScreen() + printStartupScreen(earlyModelFlag) // For all other paths, load the startup profiler const {