diff --git a/src/utils/providerValidation.test.ts b/src/utils/providerValidation.test.ts index 3917638f..d88fef77 100644 --- a/src/utils/providerValidation.test.ts +++ b/src/utils/providerValidation.test.ts @@ -3,6 +3,9 @@ import { afterEach, expect, test } from 'bun:test' import { getProviderValidationError } from './providerValidation.ts' const originalEnv = { + CLAUDE_CODE_USE_OPENAI: process.env.CLAUDE_CODE_USE_OPENAI, + OPENAI_API_KEY: process.env.OPENAI_API_KEY, + OPENAI_BASE_URL: process.env.OPENAI_BASE_URL, CLAUDE_CODE_USE_GEMINI: process.env.CLAUDE_CODE_USE_GEMINI, GEMINI_API_KEY: process.env.GEMINI_API_KEY, GOOGLE_API_KEY: process.env.GOOGLE_API_KEY, @@ -20,6 +23,9 @@ function restoreEnv(key: string, value: string | undefined): void { } afterEach(() => { + restoreEnv('CLAUDE_CODE_USE_OPENAI', originalEnv.CLAUDE_CODE_USE_OPENAI) + restoreEnv('OPENAI_API_KEY', originalEnv.OPENAI_API_KEY) + restoreEnv('OPENAI_BASE_URL', originalEnv.OPENAI_BASE_URL) restoreEnv('CLAUDE_CODE_USE_GEMINI', originalEnv.CLAUDE_CODE_USE_GEMINI) restoreEnv('GEMINI_API_KEY', originalEnv.GEMINI_API_KEY) restoreEnv('GOOGLE_API_KEY', originalEnv.GOOGLE_API_KEY) @@ -71,3 +77,19 @@ test('still errors when no Gemini credential source is available', async () => { 'GEMINI_API_KEY, GOOGLE_API_KEY, GEMINI_ACCESS_TOKEN, or Google ADC credentials are required when CLAUDE_CODE_USE_GEMINI=1.', ) }) + +test('openai missing key error includes recovery guidance and config locations', async () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1' + delete process.env.OPENAI_API_KEY + + const message = await getProviderValidationError(process.env) + expect(message).toContain( + 'OPENAI_API_KEY is required when CLAUDE_CODE_USE_OPENAI=1 and OPENAI_BASE_URL is not local.', + ) + expect(message).toContain( + 'set CLAUDE_CODE_USE_OPENAI=0 in your shell environment', + ) + expect(message).toContain('Saved startup settings can come from') + expect(message).toContain('.openclaude-profile.json') +}) diff --git a/src/utils/providerValidation.ts b/src/utils/providerValidation.ts index d1e3d353..5f462c35 100644 --- a/src/utils/providerValidation.ts +++ b/src/utils/providerValidation.ts @@ -1,14 +1,16 @@ +import { resolve } from 'node:path' import { getGithubEndpointType, isLocalProviderUrl, resolveCodexApiCredentials, resolveProviderRequest, } from '../services/api/providerConfig.js' +import { getGlobalClaudeFile } from './env.js' import { type GeminiResolvedCredential, resolveGeminiCredential, } from './geminiAuth.js' -import { redactSecretValueForDisplay } from './providerProfile.js' +import { PROFILE_FILE_NAME, redactSecretValueForDisplay } from './providerProfile.js' function isEnvTruthy(value: string | undefined): boolean { if (!value) return false @@ -61,6 +63,17 @@ function checkGithubTokenStatus( return 'valid' } +function getOpenAIMissingKeyMessage(): string { + const globalConfigPath = getGlobalClaudeFile() + const profilePath = resolve(process.cwd(), PROFILE_FILE_NAME) + + return [ + 'OPENAI_API_KEY is required when CLAUDE_CODE_USE_OPENAI=1 and OPENAI_BASE_URL is not local.', + `To recover, run /provider and switch provider, or set CLAUDE_CODE_USE_OPENAI=0 in your shell environment.`, + `Saved startup settings can come from ${globalConfigPath} or ${profilePath}.`, + ].join('\n') +} + export async function getProviderValidationError( env: NodeJS.ProcessEnv = process.env, options?: { @@ -137,7 +150,7 @@ export async function getProviderValidationError( if (useGithub && hasGithubToken) { return null } - return 'OPENAI_API_KEY is required when CLAUDE_CODE_USE_OPENAI=1 and OPENAI_BASE_URL is not local.' + return getOpenAIMissingKeyMessage() } return null