From 690cf1f281fc3d6ac76cd106ba754c9b8ea29d18 Mon Sep 17 00:00:00 2001 From: DhanushSantosh Date: Wed, 14 Jan 2026 00:45:01 +0530 Subject: [PATCH] fix(codex-provider): use SDK mode when API key is present to avoid OAuth failures When an OpenAI API key is stored in settings or environment, use SDK mode instead of CLI mode. This bypasses the MCP transport layer which was failing with 'TokenRefreshFailed' errors due to OAuth token issues. The SDK uses the API key directly via @openai/codex-sdk, avoiding the OAuth token refresh mechanism that was causing mid-execution failures. --- apps/server/src/providers/codex-provider.ts | 56 +++++++++++++++---- .../unit/providers/codex-provider.test.ts | 6 +- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/apps/server/src/providers/codex-provider.ts b/apps/server/src/providers/codex-provider.ts index 2e3962a0..e0f38ee9 100644 --- a/apps/server/src/providers/codex-provider.ts +++ b/apps/server/src/providers/codex-provider.ts @@ -45,6 +45,7 @@ import { getCodexTodoToolName, } from './codex-tool-mapping.js'; import { SettingsService } from '../services/settings-service.js'; +import { createTempEnvOverride } from '../lib/auth-utils.js'; import { checkSandboxCompatibility } from '../lib/sdk-options.js'; import { CODEX_MODELS } from './codex-models.js'; @@ -142,6 +143,7 @@ type CodexExecutionMode = typeof CODEX_EXECUTION_MODE_CLI | typeof CODEX_EXECUTI type CodexExecutionPlan = { mode: CodexExecutionMode; cliPath: string | null; + openAiApiKey?: string | null; }; const ALLOWED_ENV_VARS = [ @@ -166,6 +168,22 @@ function buildEnv(): Record { return env; } +async function resolveOpenAiApiKey(): Promise { + const envKey = process.env[OPENAI_API_KEY_ENV]; + if (envKey) { + return envKey; + } + + try { + const settingsService = new SettingsService(getCodexSettingsDir()); + const credentials = await settingsService.getCredentials(); + const storedKey = credentials.apiKeys.openai?.trim(); + return storedKey ? storedKey : null; + } catch { + return null; + } +} + function hasMcpServersConfigured(options: ExecuteOptions): boolean { return Boolean(options.mcpServers && Object.keys(options.mcpServers).length > 0); } @@ -181,18 +199,21 @@ function isSdkEligible(options: ExecuteOptions): boolean { async function resolveCodexExecutionPlan(options: ExecuteOptions): Promise { const cliPath = await findCodexCliPath(); const authIndicators = await getCodexAuthIndicators(); - const hasApiKey = Boolean(process.env[OPENAI_API_KEY_ENV]); + const openAiApiKey = await resolveOpenAiApiKey(); + const hasApiKey = Boolean(openAiApiKey); const cliAuthenticated = authIndicators.hasOAuthToken || authIndicators.hasApiKey || hasApiKey; const sdkEligible = isSdkEligible(options); const cliAvailable = Boolean(cliPath); + if (hasApiKey) { + return { + mode: CODEX_EXECUTION_MODE_SDK, + cliPath, + openAiApiKey, + }; + } + if (sdkEligible) { - if (hasApiKey) { - return { - mode: CODEX_EXECUTION_MODE_SDK, - cliPath, - }; - } if (!cliAvailable) { throw new Error(ERROR_CODEX_SDK_AUTH_REQUIRED); } @@ -209,6 +230,7 @@ async function resolveCodexExecutionPlan(options: ExecuteOptions): Promise { const cliPath = await findCodexCliPath(); - const hasApiKey = !!process.env[OPENAI_API_KEY_ENV]; + const hasApiKey = Boolean(await resolveOpenAiApiKey()); const authIndicators = await getCodexAuthIndicators(); const installed = !!cliPath; @@ -1013,7 +1047,7 @@ export class CodexProvider extends BaseProvider { */ async checkAuth(): Promise { const cliPath = await findCodexCliPath(); - const hasApiKey = !!process.env[OPENAI_API_KEY_ENV]; + const hasApiKey = Boolean(await resolveOpenAiApiKey()); const authIndicators = await getCodexAuthIndicators(); // Check for API key in environment diff --git a/apps/server/tests/unit/providers/codex-provider.test.ts b/apps/server/tests/unit/providers/codex-provider.test.ts index ada1aae1..6ca69d86 100644 --- a/apps/server/tests/unit/providers/codex-provider.test.ts +++ b/apps/server/tests/unit/providers/codex-provider.test.ts @@ -257,7 +257,7 @@ describe('codex-provider.ts', () => { expect(results[1].result).toBe('Hello from SDK'); }); - it('uses the CLI when tools are requested even if an API key is present', async () => { + it('uses the SDK when API key is present, even for tool requests (to avoid OAuth issues)', async () => { process.env[OPENAI_API_KEY_ENV] = 'sk-test'; vi.mocked(spawnJSONLProcess).mockReturnValue((async function* () {})()); @@ -270,8 +270,8 @@ describe('codex-provider.ts', () => { }) ); - expect(codexRunMock).not.toHaveBeenCalled(); - expect(spawnJSONLProcess).toHaveBeenCalled(); + expect(codexRunMock).toHaveBeenCalled(); + expect(spawnJSONLProcess).not.toHaveBeenCalled(); }); it('falls back to CLI when no tools are requested and no API key is available', async () => {