From e2f98d85a065cf008007a57fa0629ea7c4e3b705 Mon Sep 17 00:00:00 2001 From: Bl0ck154 <36800583+Bl0ck154@users.noreply.github.com> Date: Sat, 29 Aug 2026 00:34:04 +0300 Subject: [PATCH] fix(codex): restore imported account state --- src/lib/oauth/services/codexImport.ts | 30 ++++++++++++++++++++++++++- tests/unit/codexBulkImport.test.ts | 14 +++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/lib/oauth/services/codexImport.ts b/src/lib/oauth/services/codexImport.ts index aa8a362c449..e7fbf8726fb 100644 --- a/src/lib/oauth/services/codexImport.ts +++ b/src/lib/oauth/services/codexImport.ts @@ -27,8 +27,19 @@ export type CodexImportPayload = { email: string; expiresAt: string; testStatus: "active"; + isActive: true; + errorCode: null; + lastError: null; + lastErrorAt: null; + lastErrorType: null; + lastErrorSource: null; + backoffLevel: 0; + rateLimitedUntil: null; + priority?: number; providerSpecificData?: { chatgptAccountId?: string; + // Canonical alias consumed by the existing Codex workspace upsert path. + workspaceId?: string; chatgptPlanType?: string; }; }; @@ -224,7 +235,12 @@ export function normalizeCodexImportRecord(input: unknown): NormalizeResult { const expiresAt = parseExpiry(rec.expired) ?? parseAccessTokenExp(accessToken); const providerSpecificData: CodexImportPayload["providerSpecificData"] = {}; - if (chatgptAccountId) providerSpecificData.chatgptAccountId = chatgptAccountId; + if (chatgptAccountId) { + providerSpecificData.chatgptAccountId = chatgptAccountId; + // CodexSwitcher stores this stable value as account_id; mirror it into + // workspaceId so createProviderConnection performs its existing upsert. + providerSpecificData.workspaceId = chatgptAccountId; + } if (chatgptPlanType) providerSpecificData.chatgptPlanType = chatgptPlanType; const payload: CodexImportPayload = { @@ -235,8 +251,20 @@ export function normalizeCodexImportRecord(input: unknown): NormalizeResult { email, expiresAt, testStatus: "active", + // Fresh imported OAuth credentials supersede a previous refresh failure. + isActive: true, + errorCode: null, + lastError: null, + lastErrorAt: null, + lastErrorType: null, + lastErrorSource: null, + backoffLevel: 0, + rateLimitedUntil: null, }; if (idToken) payload.idToken = idToken; + if (typeof rec.priority === "number" && Number.isInteger(rec.priority) && rec.priority > 0) { + payload.priority = rec.priority; + } if (Object.keys(providerSpecificData).length > 0) { payload.providerSpecificData = providerSpecificData; } diff --git a/tests/unit/codexBulkImport.test.ts b/tests/unit/codexBulkImport.test.ts index 9576b83109e..ce7b8b18d91 100644 --- a/tests/unit/codexBulkImport.test.ts +++ b/tests/unit/codexBulkImport.test.ts @@ -61,10 +61,19 @@ describe("normalizeCodexImportRecord", () => { assert.equal(payload.refreshToken, "refresh-xyz"); assert.equal(payload.idToken, FULL_RECORD.id_token); assert.equal(payload.testStatus, "active"); + assert.equal(payload.isActive, true); + assert.equal(payload.errorCode, null); + assert.equal(payload.lastError, null); + assert.equal(payload.lastErrorAt, null); + assert.equal(payload.lastErrorType, null); + assert.equal(payload.lastErrorSource, null); + assert.equal(payload.backoffLevel, 0); + assert.equal(payload.rateLimitedUntil, null); // JWT email wins over the record-level email. assert.equal(payload.email, "test-jwt@example.com"); assert.deepEqual(payload.providerSpecificData, { chatgptAccountId: "acct-from-jwt", + workspaceId: "acct-from-jwt", chatgptPlanType: "plus", }); // ISO-formatted, parses back to the right instant. @@ -87,6 +96,7 @@ describe("normalizeCodexImportRecord", () => { if (!result.ok) return; assert.equal(result.payload.email, "fallback@example.com"); assert.equal(result.payload.providerSpecificData?.chatgptAccountId, "x"); + assert.equal(result.payload.providerSpecificData?.workspaceId, "x"); }); test("falls back to account_id when id_token is missing", () => { @@ -99,6 +109,7 @@ describe("normalizeCodexImportRecord", () => { assert.equal(result.ok, true); if (!result.ok) return; assert.equal(result.payload.providerSpecificData?.chatgptAccountId, "acct-top-level"); + assert.equal(result.payload.providerSpecificData?.workspaceId, "acct-top-level"); assert.equal(result.payload.idToken, undefined); }); @@ -205,6 +216,7 @@ describe("normalizeCodexImportRecord", () => { assert.equal(result.payload.email, "cli@example.com"); assert.deepEqual(result.payload.providerSpecificData, { chatgptAccountId: "acct-cli", + workspaceId: "acct-cli", chatgptPlanType: "free", }); // Falls back to the access_token's `exp` claim when no `expired` field exists. @@ -283,6 +295,7 @@ describe("9router camelCase Codex export (#6665)", () => { assert.equal(payload.email, "jwt-9r@example.com"); assert.deepEqual(payload.providerSpecificData, { chatgptAccountId: "acct-jwt-9r", + workspaceId: "acct-jwt-9r", chatgptPlanType: "plus", }); // camelCase `expiresAt` is honored as the expiry source. @@ -301,6 +314,7 @@ describe("9router camelCase Codex export (#6665)", () => { assert.equal(result.payload.email, "no-jwt-9r@example.com"); assert.deepEqual(result.payload.providerSpecificData, { chatgptAccountId: "acct-psd", + workspaceId: "acct-psd", chatgptPlanType: "team", }); });