Skip to content

Commit c6531ab

Browse files
committed
fix(auth): cooldown live accounts on zero grouped removal
Use refresh-token scoped cooldown updates to avoid mutating stale account references when grouped removal returns zero.
1 parent 21601d2 commit c6531ab

File tree

3 files changed

+28
-6
lines changed

3 files changed

+28
-6
lines changed

index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2167,11 +2167,16 @@ while (attempted.size < Math.max(1, accountCount)) {
21672167
logWarn(
21682168
`[${PLUGIN_NAME}] Expected grouped account removal after auth failures, but removed ${removedCount}.`,
21692169
);
2170-
accountManager.markAccountCoolingDown(
2171-
account,
2170+
const cooledCount = accountManager.markAccountsWithRefreshTokenCoolingDown(
2171+
account.refreshToken,
21722172
ACCOUNT_LIMITS.AUTH_FAILURE_COOLDOWN_MS,
21732173
"auth-failure",
21742174
);
2175+
if (cooledCount <= 0) {
2176+
logWarn(
2177+
`[${PLUGIN_NAME}] Unable to apply auth-failure cooldown; no live account found for refresh token.`,
2178+
);
2179+
}
21752180
accountManager.saveToDiskDebounced();
21762181
continue;
21772182
}

lib/accounts.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,22 @@ export class AccountManager {
695695
account.cooldownReason = reason;
696696
}
697697

698+
/**
699+
* Mark every in-memory account sharing a refresh token as cooling down.
700+
* @returns Number of live accounts updated.
701+
*/
702+
markAccountsWithRefreshTokenCoolingDown(
703+
refreshToken: string,
704+
cooldownMs: number,
705+
reason: CooldownReason,
706+
): number {
707+
const matches = this.accounts.filter((account) => account.refreshToken === refreshToken);
708+
for (const account of matches) {
709+
this.markAccountCoolingDown(account, cooldownMs, reason);
710+
}
711+
return matches.length;
712+
}
713+
698714
isAccountCoolingDown(account: ManagedAccount): boolean {
699715
if (account.coolingDownUntil === undefined) return false;
700716
if (nowMs() >= account.coolingDownUntil) {

test/index.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ vi.mock("../lib/accounts.js", () => {
328328
incrementAuthFailures() { return 1; }
329329
async saveToDisk() {}
330330
markAccountCoolingDown() {}
331+
markAccountsWithRefreshTokenCoolingDown() { return 1; }
331332
markRateLimited() {}
332333
markRateLimitedWithReason() {}
333334
consumeToken() { return true; }
@@ -1348,9 +1349,9 @@ describe("OpenAIOAuthPlugin fetch handler", () => {
13481349
const removeGroupedAccountsSpy = vi
13491350
.spyOn(AccountManager.prototype, "removeAccountsWithSameRefreshToken")
13501351
.mockReturnValue(0);
1351-
const markAccountCoolingDownSpy = vi.spyOn(
1352+
const markAccountsWithRefreshTokenCoolingDownSpy = vi.spyOn(
13521353
AccountManager.prototype,
1353-
"markAccountCoolingDown",
1354+
"markAccountsWithRefreshTokenCoolingDown",
13541355
);
13551356

13561357
globalThis.fetch = vi.fn().mockResolvedValue(
@@ -1367,8 +1368,8 @@ describe("OpenAIOAuthPlugin fetch handler", () => {
13671368
expect(globalThis.fetch).not.toHaveBeenCalled();
13681369
expect(incrementAuthFailuresSpy).toHaveBeenCalledTimes(1);
13691370
expect(removeGroupedAccountsSpy).toHaveBeenCalledTimes(1);
1370-
expect(markAccountCoolingDownSpy).toHaveBeenCalledWith(
1371-
expect.objectContaining({ index: 0 }),
1371+
expect(markAccountsWithRefreshTokenCoolingDownSpy).toHaveBeenCalledWith(
1372+
"refresh-1",
13721373
ACCOUNT_LIMITS.AUTH_FAILURE_COOLDOWN_MS,
13731374
"auth-failure",
13741375
);

0 commit comments

Comments
 (0)