From f1cc8f0cfc7c98f0235dcd5a2cb2fd5b70dcdaf5 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 18 Apr 2026 09:03:14 -0700 Subject: [PATCH] fix(codex): reuse bound auth profile for app-server startup --- .../codex/src/app-server/run-attempt.test.ts | 50 +++++++++++++++++++ .../codex/src/app-server/run-attempt.ts | 6 ++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/extensions/codex/src/app-server/run-attempt.test.ts b/extensions/codex/src/app-server/run-attempt.test.ts index 8fc9dfc3f67..4c505bbda40 100644 --- a/extensions/codex/src/app-server/run-attempt.test.ts +++ b/extensions/codex/src/app-server/run-attempt.test.ts @@ -558,4 +558,54 @@ describe("runCodexAppServerAttempt", () => { expect(binding.authProfileId).toBe("openai-codex:bound"); }); + + it("reuses the bound auth profile for app-server startup when params omit it", async () => { + const sessionFile = path.join(tempDir, "session.jsonl"); + const workspaceDir = path.join(tempDir, "workspace"); + await writeCodexAppServerBinding(sessionFile, { + threadId: "thread-existing", + cwd: workspaceDir, + authProfileId: "openai-codex:bound", + model: "gpt-5.4-codex", + modelProvider: "openai", + dynamicToolsFingerprint: "[]", + }); + const seenAuthProfileIds: Array = []; + let notify: (notification: CodexServerNotification) => Promise = async () => undefined; + __testing.setCodexAppServerClientFactoryForTests(async (_startOptions, authProfileId) => { + seenAuthProfileIds.push(authProfileId); + return { + request: async (method: string) => { + if (method === "thread/resume") { + return { thread: { id: "thread-existing" }, modelProvider: "openai" }; + } + if (method === "turn/start") { + return { turn: { id: "turn-1", status: "inProgress" } }; + } + throw new Error(`unexpected method: ${method}`); + }, + addNotificationHandler: (handler: typeof notify) => { + notify = handler; + return () => undefined; + }, + addRequestHandler: () => () => undefined, + } as never; + }); + const params = createParams(sessionFile, workspaceDir); + delete params.authProfileId; + + const run = runCodexAppServerAttempt(params); + await vi.waitFor(() => expect(seenAuthProfileIds).toEqual(["openai-codex:bound"])); + await notify({ + method: "turn/completed", + params: { + threadId: "thread-existing", + turnId: "turn-1", + turn: { id: "turn-1", status: "completed" }, + }, + }); + await run; + + expect(seenAuthProfileIds).toEqual(["openai-codex:bound"]); + }); }); diff --git a/extensions/codex/src/app-server/run-attempt.ts b/extensions/codex/src/app-server/run-attempt.ts index 9a54ea947af..882abf453c7 100644 --- a/extensions/codex/src/app-server/run-attempt.ts +++ b/extensions/codex/src/app-server/run-attempt.ts @@ -30,7 +30,7 @@ import { type JsonObject, type JsonValue, } from "./protocol.js"; -import type { CodexAppServerThreadBinding } from "./session-binding.js"; +import { readCodexAppServerBinding, type CodexAppServerThreadBinding } from "./session-binding.js"; import { clearSharedCodexAppServerClient, getSharedCodexAppServerClient } from "./shared-client.js"; import { buildTurnStartParams, startOrResumeThread } from "./thread-lifecycle.js"; import { mirrorCodexAppServerTranscript } from "./transcript-mirror.js"; @@ -79,6 +79,8 @@ export async function runCodexAppServerAttempt( agentId: params.agentId, }); let yieldDetected = false; + const startupBinding = await readCodexAppServerBinding(params.sessionFile); + const startupAuthProfileId = params.authProfileId ?? startupBinding?.authProfileId; const tools = await buildDynamicTools({ params, resolvedWorkspace, @@ -102,7 +104,7 @@ export async function runCodexAppServerAttempt( timeoutMs: params.timeoutMs, signal: runAbortController.signal, operation: async () => { - const startupClient = await clientFactory(appServer.start, params.authProfileId); + const startupClient = await clientFactory(appServer.start, startupAuthProfileId); const startupThread = await startOrResumeThread({ client: startupClient, params,