Skip to content

Commit 7f512c3

Browse files
committed
fix: heal stale cooldown metadata on success
1 parent adbf49a commit 7f512c3

2 files changed

Lines changed: 52 additions & 10 deletions

File tree

lib/accounts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ export class AccountManager {
557557
let healed = false;
558558

559559
if (!isCoolingDown && hadCooldownMetadata) {
560+
this.clearAccountCooldown(account);
560561
healed = true;
561562
}
562563

test/accounts.test.ts

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1903,7 +1903,11 @@ describe("AccountManager", () => {
19031903
expect(score).toBe(100);
19041904
});
19051905

1906-
it("recordSuccess clears stale auth failure state and persists the healed account", () => {
1906+
it("recordSuccess clears stale auth failure state and persists the healed account", async () => {
1907+
const { saveAccounts } = await import("../lib/storage.js");
1908+
const mockSaveAccounts = vi.mocked(saveAccounts);
1909+
mockSaveAccounts.mockClear();
1910+
19071911
const now = Date.now();
19081912
const stored = {
19091913
version: 3 as const,
@@ -1923,19 +1927,58 @@ describe("AccountManager", () => {
19231927
const manager = new AccountManager(undefined, stored);
19241928
const account = manager.getCurrentAccount()!;
19251929
account.consecutiveAuthFailures = 2;
1926-
const saveSpy = vi
1927-
.spyOn(manager, "saveToDiskDebounced")
1928-
.mockImplementation(() => {});
19291930

19301931
manager.recordSuccess(account, "codex", "gpt-5.1");
1932+
await manager.flushPendingSave();
19311933

19321934
expect(account.consecutiveAuthFailures).toBe(0);
19331935
expect(account.coolingDownUntil).toBeUndefined();
19341936
expect(account.cooldownReason).toBeUndefined();
1935-
expect(saveSpy).toHaveBeenCalledTimes(1);
1937+
expect(mockSaveAccounts).toHaveBeenCalledTimes(1);
1938+
const persisted = mockSaveAccounts.mock.calls[0]?.[0];
1939+
expect(persisted?.accounts[0]?.consecutiveAuthFailures ?? 0).toBe(0);
1940+
expect(persisted?.accounts[0]?.coolingDownUntil).toBeUndefined();
1941+
expect(persisted?.accounts[0]?.cooldownReason).toBeUndefined();
19361942
});
19371943

1938-
it("recordSuccess does not clear an active cooldown from a newer concurrent failure", () => {
1944+
it("recordSuccess clears stale cooldown metadata when only the reason remains", async () => {
1945+
const { saveAccounts } = await import("../lib/storage.js");
1946+
const mockSaveAccounts = vi.mocked(saveAccounts);
1947+
mockSaveAccounts.mockClear();
1948+
1949+
const now = Date.now();
1950+
const stored = {
1951+
version: 3 as const,
1952+
activeIndex: 0,
1953+
accounts: [
1954+
{
1955+
refreshToken: "token-1",
1956+
addedAt: now,
1957+
lastUsed: now,
1958+
cooldownReason: "network-error" as const,
1959+
},
1960+
],
1961+
};
1962+
1963+
const manager = new AccountManager(undefined, stored);
1964+
const account = manager.getCurrentAccount()!;
1965+
1966+
manager.recordSuccess(account, "codex", "gpt-5.1");
1967+
await manager.flushPendingSave();
1968+
1969+
expect(account.coolingDownUntil).toBeUndefined();
1970+
expect(account.cooldownReason).toBeUndefined();
1971+
expect(mockSaveAccounts).toHaveBeenCalledTimes(1);
1972+
const persisted = mockSaveAccounts.mock.calls[0]?.[0];
1973+
expect(persisted?.accounts[0]?.coolingDownUntil).toBeUndefined();
1974+
expect(persisted?.accounts[0]?.cooldownReason).toBeUndefined();
1975+
});
1976+
1977+
it("recordSuccess does not clear an active cooldown from a newer concurrent failure", async () => {
1978+
const { saveAccounts } = await import("../lib/storage.js");
1979+
const mockSaveAccounts = vi.mocked(saveAccounts);
1980+
mockSaveAccounts.mockClear();
1981+
19391982
const now = Date.now();
19401983
const stored = {
19411984
version: 3 as const,
@@ -1955,16 +1998,14 @@ describe("AccountManager", () => {
19551998
const manager = new AccountManager(undefined, stored);
19561999
const account = manager.getCurrentAccount()!;
19572000
account.consecutiveAuthFailures = 2;
1958-
const saveSpy = vi
1959-
.spyOn(manager, "saveToDiskDebounced")
1960-
.mockImplementation(() => {});
19612001

19622002
manager.recordSuccess(account, "codex", "gpt-5.1");
2003+
await manager.flushPendingSave();
19632004

19642005
expect(account.consecutiveAuthFailures).toBe(2);
19652006
expect(account.coolingDownUntil).toBe(now + 60_000);
19662007
expect(account.cooldownReason).toBe("auth-failure");
1967-
expect(saveSpy).not.toHaveBeenCalled();
2008+
expect(mockSaveAccounts).not.toHaveBeenCalled();
19682009
});
19692010

19702011
it("recordRateLimit updates health and drains token bucket", () => {

0 commit comments

Comments
 (0)