From d194ca625643d3adb87ecafd26c876c0e0793ed7 Mon Sep 17 00:00:00 2001 From: Vincent Ye Date: Fri, 21 Aug 2026 16:35:16 -0700 Subject: [PATCH] fix: refresh Codex readiness after ChatGPT login --- src/agent/chatgpt-auth-state-machine.ts | 27 ++++++++++++++------- test/unit/codex-app-server-auth.test.ts | 31 +++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/agent/chatgpt-auth-state-machine.ts b/src/agent/chatgpt-auth-state-machine.ts index 214e0ec..c5350e6 100644 --- a/src/agent/chatgpt-auth-state-machine.ts +++ b/src/agent/chatgpt-auth-state-machine.ts @@ -311,12 +311,8 @@ export class ChatGptAuthStateMachine implements ChatGptSetupController { this.#status = { state: "not_connected" }; } await this.refreshCapabilities(); - if ( - this.#accountState === "connected" && - this.#status.state !== "starting" && - this.#status.state !== "awaiting_authorization" - ) { - this.#status = { state: "connected" }; + if (this.#accountState === "connected") { + await this.#transitionToConnected(); } return; } @@ -364,10 +360,25 @@ export class ChatGptAuthStateMachine implements ChatGptSetupController { try { await this.refreshCapabilities(); if (this.#accountState !== "connected") { - this.#loginId = undefined; - this.#status = { state: "failed", code: "CHATGPT_LOGIN_FAILED" }; + // App Server emits login completion before reloading its account. + // Keep waiting for the authoritative account/updated notification. return; } + await this.#transitionToConnected(); + } catch { + this.#loginId = undefined; + this.#status = { + state: "failed", + code: "CHATGPT_CREDENTIAL_SAVE_FAILED", + }; + } + } + + async #transitionToConnected(): Promise { + if (this.#status.state === "connected") { + return; + } + try { await validateAndRestrictCodexAuthFile(this.#codexHome); } catch { this.#loginId = undefined; diff --git a/test/unit/codex-app-server-auth.test.ts b/test/unit/codex-app-server-auth.test.ts index a6430ec..ad803f9 100644 --- a/test/unit/codex-app-server-auth.test.ts +++ b/test/unit/codex-app-server-auth.test.ts @@ -138,7 +138,7 @@ async function temporaryCodexHome(): Promise { } describe("Codex App Server ChatGPT authentication", () => { - it("performs the pinned App Server handshake and exposes only the device ceremony", async () => { + it("waits for the pinned App Server account update before completing device login", async () => { const codexHome = await temporaryCodexHome(); const connection = new FakeConnection(codexHome); const connected = vi.fn(); @@ -180,13 +180,27 @@ describe("Codex App Server ChatGPT authentication", () => { }); await writeFile(join(codexHome, "auth.json"), "fixture", { mode: 0o600 }); - connection.connected = true; connection.emit("account/login/completed", { loginId: "3ea32ef5-f9b0-4d0e-b59c-d9838db91f92", success: true, error: null, onboardingEntrypoint: null, }); + await vi.waitFor(() => + expect( + connection.requests.filter( + (request) => request.method === "account/read", + ), + ).toHaveLength(2), + ); + expect(auth.status()).toMatchObject({ state: "awaiting_authorization" }); + expect(connected).not.toHaveBeenCalled(); + + connection.connected = true; + connection.emit("account/updated", { + authMode: "chatgpt", + planType: "plus", + }); await vi.waitFor(() => expect(auth.status()).toEqual({ state: "connected" })); expect(connected).toHaveBeenCalledOnce(); expect(auth.capabilities()).toMatchObject({ @@ -194,6 +208,19 @@ describe("Codex App Server ChatGPT authentication", () => { planType: "plus", models: [{ id: "gpt-5.6-luna" }], }); + + connection.emit("account/updated", { + authMode: "chatgpt", + planType: "plus", + }); + await vi.waitFor(() => + expect( + connection.requests.filter( + (request) => request.method === "account/read", + ), + ).toHaveLength(4), + ); + expect(connected).toHaveBeenCalledOnce(); await auth.close(); expect(connection.closed).toBe(true); });