diff --git a/extensions/acpx/src/runtime.ts b/extensions/acpx/src/runtime.ts index e1f0024c699..d876240ebb3 100644 --- a/extensions/acpx/src/runtime.ts +++ b/extensions/acpx/src/runtime.ts @@ -936,6 +936,9 @@ export class AcpxRuntime implements AcpRuntime { stripProviderAuthEnvVars: this.config.stripProviderAuthEnvVars, spawnOptions: this.spawnCommandOptions, }); + if (!targetCommand) { + return null; + } const resolved = buildMcpProxyAgentCommand({ targetCommand, mcpServers: toAcpMcpServers(this.config.mcpServers), diff --git a/extensions/openai/openai-provider.test.ts b/extensions/openai/openai-provider.test.ts index 08390c9df96..979499d09a1 100644 --- a/extensions/openai/openai-provider.test.ts +++ b/extensions/openai/openai-provider.test.ts @@ -1,8 +1,14 @@ import OpenAI from "openai"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { buildOpenAICodexProviderPlugin } from "./openai-codex-provider.js"; import { buildOpenAIProvider } from "./openai-provider.js"; +const getCodexOAuthApiKeyMock = vi.hoisted(() => vi.fn()); + +vi.mock("./openai-codex-provider.runtime.js", () => ({ + getOAuthApiKey: getCodexOAuthApiKeyMock, +})); + const OPENAI_API_KEY = process.env.OPENAI_API_KEY ?? ""; const DEFAULT_LIVE_MODEL_IDS = ["gpt-5.4-mini", "gpt-5.4-nano"] as const; const liveEnabled = OPENAI_API_KEY.trim().length > 0 && process.env.OPENCLAW_LIVE_TEST === "1"; @@ -222,6 +228,24 @@ describe("buildOpenAIProvider", () => { } as never), ).toBe(true); }); + + it("falls back to cached codex oauth credentials on accountId extraction failures", async () => { + const provider = buildOpenAICodexProviderPlugin(); + const credential = { + type: "oauth" as const, + provider: "openai-codex", + access: "cached-access-token", + refresh: "refresh-token", + expires: Date.now() - 60_000, + }; + + getCodexOAuthApiKeyMock.mockReset(); + getCodexOAuthApiKeyMock.mockRejectedValueOnce( + new Error("Failed to extract accountId from token"), + ); + + await expect(provider.refreshOAuth?.(credential)).resolves.toEqual(credential); + }); }); describeLive("buildOpenAIProvider live", () => { diff --git a/src/plugins/contracts/runtime.contract.test.ts b/src/plugins/contracts/runtime.contract.test.ts index 3d356dc3ac6..09b36576ff4 100644 --- a/src/plugins/contracts/runtime.contract.test.ts +++ b/src/plugins/contracts/runtime.contract.test.ts @@ -28,6 +28,10 @@ vi.mock("@mariozechner/pi-ai/oauth", async () => { }; }); +vi.mock("../../../extensions/openai/src/openai-codex-provider.runtime.js", () => ({ + getOAuthApiKey: getOAuthApiKeyMock, +})); + function createModel(overrides: Partial & Pick) { return { id: overrides.id,