Skip to content

Commit 4973a20

Browse files
committed
Fix remaining workspace binding review issues
1 parent 223bdc1 commit 4973a20

2 files changed

Lines changed: 86 additions & 13 deletions

File tree

lib/codex-manager.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -929,7 +929,7 @@ function resolveStoredAccountIdentity(
929929

930930
return {
931931
accountId,
932-
accountIdSource: tokenAccountId ? "token" : storedAccountIdSource,
932+
accountIdSource: accountId === tokenAccountId ? "token" : storedAccountIdSource,
933933
};
934934
}
935935

@@ -942,6 +942,9 @@ function applyTokenAccountIdentity(
942942
account.accountIdSource,
943943
tokenAccountId,
944944
);
945+
if (!nextIdentity.accountId) {
946+
return false;
947+
}
945948
if (nextIdentity.accountId === account.accountId
946949
&& nextIdentity.accountIdSource === account.accountIdSource) {
947950
return false;
@@ -2545,8 +2548,11 @@ function upsertRecoveredFlaggedAccount(
25452548
changed = true;
25462549
}
25472550
if (
2548-
(nextAccountId !== existing.accountId)
2549-
|| (nextAccountIdSource !== existing.accountIdSource)
2551+
nextAccountId !== undefined &&
2552+
(
2553+
(nextAccountId !== existing.accountId)
2554+
|| (nextAccountIdSource !== existing.accountIdSource)
2555+
)
25502556
) {
25512557
existing.accountId = nextAccountId;
25522558
existing.accountIdSource = nextAccountIdSource;
@@ -2988,7 +2994,7 @@ async function runFix(args: string[]): Promise<number> {
29882994
}
29892995
if (!account.accountId && nextAccountId) {
29902996
account.accountId = nextAccountId;
2991-
account.accountIdSource = account.accountIdSource ?? "token";
2997+
account.accountIdSource = "token";
29922998
accountChanged = true;
29932999
}
29943000

@@ -3307,7 +3313,7 @@ function applyDoctorFixes(storage: AccountStorageV3): { changed: boolean; action
33073313
const tokenAccountId = extractAccountId(account.accessToken);
33083314
if (!account.accountId && tokenAccountId) {
33093315
account.accountId = tokenAccountId;
3310-
account.accountIdSource = account.accountIdSource ?? "token";
3316+
account.accountIdSource = "token";
33113317
changed = true;
33123318
actions.push({
33133319
key: "account-id-from-token",
@@ -3675,9 +3681,7 @@ async function runDoctor(args: string[]): Promise<number> {
36753681
activeAccount.refreshToken = refreshResult.refresh;
36763682
activeAccount.expiresAt = refreshResult.expires;
36773683
if (refreshedEmail) activeAccount.email = refreshedEmail;
3678-
if (applyTokenAccountIdentity(activeAccount, refreshedAccountId)) {
3679-
storageChangedFromDoctorSync = true;
3680-
}
3684+
applyTokenAccountIdentity(activeAccount, refreshedAccountId);
36813685
syncAccessToken = refreshResult.access;
36823686
syncRefreshToken = refreshResult.refresh;
36833687
syncExpiresAt = refreshResult.expires;

test/codex-manager-cli.test.ts

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ vi.mock("../lib/accounts.js", () => ({
7777
resolveRequestAccountId: vi.fn(
7878
(
7979
storedAccountId: string | undefined,
80-
source: string | undefined,
80+
currentAccountIdSource: string | undefined,
8181
tokenId: string | undefined,
8282
) => {
8383
if (!storedAccountId) return tokenId;
84-
if (source === "org" || source === "manual") {
84+
if (currentAccountIdSource === "org" || currentAccountIdSource === "manual") {
8585
return storedAccountId;
8686
}
8787
return tokenId ?? storedAccountId;
@@ -92,10 +92,10 @@ vi.mock("../lib/accounts.js", () => ({
9292
),
9393
selectBestAccountCandidate: vi.fn(() => null),
9494
shouldUpdateAccountIdFromToken: vi.fn(
95-
(source: string | undefined, currentAccountId: string | undefined) => {
95+
(currentAccountIdSource: string | undefined, currentAccountId: string | undefined) => {
9696
if (!currentAccountId) return true;
97-
if (!source) return true;
98-
return source === "token" || source === "id_token";
97+
if (!currentAccountIdSource) return true;
98+
return currentAccountIdSource === "token" || currentAccountIdSource === "id_token";
9999
},
100100
),
101101
}));
@@ -824,6 +824,75 @@ describe("codex manager cli commands", () => {
824824
extractAccountIdMock.mockImplementation(() => "acc_test");
825825
});
826826

827+
it("does not clear an existing workspace binding when flagged recovery refresh has no account id", async () => {
828+
const now = Date.now();
829+
loadFlaggedAccountsMock.mockResolvedValueOnce({
830+
version: 1,
831+
accounts: [
832+
{
833+
refreshToken: "flagged-refresh",
834+
email: "a@example.com",
835+
addedAt: now - 1_000,
836+
lastUsed: now - 1_000,
837+
flaggedAt: now - 5_000,
838+
},
839+
],
840+
});
841+
loadAccountsMock.mockResolvedValueOnce({
842+
version: 3,
843+
activeIndex: 0,
844+
activeIndexByFamily: { codex: 0 },
845+
accounts: [
846+
{
847+
refreshToken: "refresh-existing",
848+
accountId: "workspace-alpha",
849+
accountIdSource: "org",
850+
accountLabel: "Workspace Alpha [id:alpha]",
851+
email: "a@example.com",
852+
addedAt: now - 3_000,
853+
lastUsed: now - 3_000,
854+
},
855+
],
856+
});
857+
queuedRefreshMock.mockResolvedValueOnce({
858+
type: "success",
859+
access: "access-restored",
860+
refresh: "refresh-restored",
861+
expires: now + 3_600_000,
862+
});
863+
const accountsModule = await import("../lib/accounts.js");
864+
const extractAccountIdMock = vi.mocked(accountsModule.extractAccountId);
865+
extractAccountIdMock.mockImplementation((accessToken?: string) =>
866+
accessToken === "access-restored" ? undefined : "workspace-alpha",
867+
);
868+
const { runCodexMultiAuthCli } = await import("../lib/codex-manager.js");
869+
870+
const exitCode = await runCodexMultiAuthCli([
871+
"auth",
872+
"verify-flagged",
873+
"--json",
874+
]);
875+
876+
expect(exitCode).toBe(0);
877+
const savedStorage = saveAccountsMock.mock.calls.at(-1)?.[0] as {
878+
accounts: Array<{
879+
accountId?: string;
880+
accountIdSource?: string;
881+
refreshToken?: string;
882+
email?: string;
883+
}>;
884+
};
885+
expect(savedStorage.accounts[0]).toEqual(
886+
expect.objectContaining({
887+
accountId: "workspace-alpha",
888+
accountIdSource: "org",
889+
refreshToken: "refresh-restored",
890+
email: "a@example.com",
891+
}),
892+
);
893+
extractAccountIdMock.mockImplementation(() => "acc_test");
894+
});
895+
827896
it("rolls back active storage when flagged persistence fails during recovery", async () => {
828897
const now = Date.now();
829898
loadFlaggedAccountsMock.mockResolvedValueOnce({

0 commit comments

Comments
 (0)