Skip to content

Commit a4b485e

Browse files
committed
Fix overlap cleanup rollback on persist failure
1 parent 4251252 commit a4b485e

File tree

2 files changed

+80
-4
lines changed

2 files changed

+80
-4
lines changed

lib/codex-multi-auth-sync.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,14 +1294,33 @@ export async function cleanupCodexMultiAuthSyncedOverlaps(
12941294
.map((account) => normalizeTrimmedIdentity(account.refreshToken))
12951295
.filter((refreshToken): refreshToken is string => refreshToken !== undefined),
12961296
);
1297-
await persist.flagged({
1298-
version: 1,
1297+
const nextFlaggedStorage = {
1298+
version: 1 as const,
12991299
accounts: currentFlaggedStorage.accounts.filter((flaggedAccount) => {
13001300
const refreshToken = normalizeTrimmedIdentity(flaggedAccount.refreshToken);
13011301
return refreshToken !== undefined && remainingRefreshTokens.has(refreshToken);
13021302
}),
1303-
});
1304-
await persist.accounts(plan.nextStorage);
1303+
};
1304+
await persist.flagged(nextFlaggedStorage);
1305+
try {
1306+
await persist.accounts(plan.nextStorage);
1307+
} catch (accountsError) {
1308+
try {
1309+
await persist.flagged(currentFlaggedStorage);
1310+
} catch (restoreFlaggedError) {
1311+
const accountsMessage =
1312+
accountsError instanceof Error ? accountsError.message : String(accountsError);
1313+
const restoreFlaggedMessage =
1314+
restoreFlaggedError instanceof Error
1315+
? restoreFlaggedError.message
1316+
: String(restoreFlaggedError);
1317+
throw new Error(
1318+
`Failed to persist overlap cleanup accounts: ${accountsMessage}; ` +
1319+
`failed to restore flagged storage: ${restoreFlaggedMessage}`,
1320+
);
1321+
}
1322+
throw accountsError;
1323+
}
13051324
}
13061325
return plan.result;
13071326
} catch (error) {

test/codex-multi-auth-sync.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1996,6 +1996,63 @@ describe("codex-multi-auth sync", () => {
19961996
}
19971997
});
19981998

1999+
it("rolls back flagged storage when overlap cleanup account persistence fails", async () => {
2000+
const originalFlaggedStorage: FlaggedAccountStorageV1 = {
2001+
version: 1,
2002+
accounts: [
2003+
{
2004+
accountId: "flagged-sync",
2005+
organizationId: "org-sync",
2006+
refreshToken: "rt-sync",
2007+
flaggedAt: 123,
2008+
addedAt: 4,
2009+
lastUsed: 4,
2010+
},
2011+
],
2012+
};
2013+
const persist = await mockOverlapCleanupTransaction(
2014+
{
2015+
version: 3,
2016+
activeIndex: 0,
2017+
activeIndexByFamily: {},
2018+
accounts: [
2019+
{
2020+
accountId: "org-local",
2021+
organizationId: "org-local",
2022+
accountIdSource: "org",
2023+
email: "shared@example.com",
2024+
refreshToken: "rt-local",
2025+
addedAt: 5,
2026+
lastUsed: 5,
2027+
},
2028+
{
2029+
accountTags: ["codex-multi-auth-sync"],
2030+
email: "shared@example.com",
2031+
refreshToken: "rt-sync",
2032+
addedAt: 4,
2033+
lastUsed: 4,
2034+
},
2035+
],
2036+
},
2037+
{
2038+
flagged: originalFlaggedStorage,
2039+
persistAccounts: async () => {
2040+
throw new Error("persist failed");
2041+
},
2042+
},
2043+
);
2044+
2045+
const { cleanupCodexMultiAuthSyncedOverlaps } = await import("../lib/codex-multi-auth-sync.js");
2046+
await expect(cleanupCodexMultiAuthSyncedOverlaps()).rejects.toThrow("persist failed");
2047+
2048+
expect(persist.flagged).toHaveBeenCalledTimes(2);
2049+
expect(persist.flagged).toHaveBeenNthCalledWith(1, {
2050+
version: 1,
2051+
accounts: [],
2052+
});
2053+
expect(persist.flagged).toHaveBeenNthCalledWith(2, originalFlaggedStorage);
2054+
});
2055+
19992056

20002057
it("limits overlap cleanup to accounts tagged from codex-multi-auth sync", async () => {
20012058
await mockOverlapCleanupTransaction({

0 commit comments

Comments
 (0)