Skip to content

Commit 30e3ca0

Browse files
committed
fix(agents): bound auth profile block expiry
1 parent 1f6c1ea commit 30e3ca0

2 files changed

Lines changed: 52 additions & 16 deletions

File tree

src/agents/auth-profiles/usage.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
clearAuthProfileCooldown,
66
clearExpiredCooldowns,
77
isProfileInCooldown,
8+
markAuthProfileBlockedUntil,
89
markAuthProfileFailure,
910
resolveProfilesUnavailableReason,
1011
resolveProfileUnusableUntil,
@@ -784,6 +785,40 @@ describe("markAuthProfileFailure — active windows do not extend on retry", ()
784785
}
785786
});
786787

788+
describe("markAuthProfileBlockedUntil", () => {
789+
it("ignores blocked-until updates when the process clock is invalid", async () => {
790+
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(Number.NaN);
791+
const store = makeStore({});
792+
try {
793+
await markAuthProfileBlockedUntil({
794+
store,
795+
profileId: "openai-codex:default",
796+
blockedUntil: Date.parse("2030-01-01T00:00:00.000Z"),
797+
source: "codex_rate_limits",
798+
});
799+
} finally {
800+
nowSpy.mockRestore();
801+
}
802+
803+
expect(store.usageStats).toEqual({});
804+
expect(storeMocks.saveAuthProfileStore).not.toHaveBeenCalled();
805+
});
806+
807+
it("ignores blocked-until updates outside the valid Date range", async () => {
808+
const store = makeStore({});
809+
810+
await markAuthProfileBlockedUntil({
811+
store,
812+
profileId: "openai-codex:default",
813+
blockedUntil: Number.MAX_SAFE_INTEGER,
814+
source: "codex_rate_limits",
815+
});
816+
817+
expect(store.usageStats).toEqual({});
818+
expect(storeMocks.saveAuthProfileStore).not.toHaveBeenCalled();
819+
});
820+
});
821+
787822
describe("markAuthProfileFailure — WHAM-aware Codex cooldowns", () => {
788823
function mockWhamResponse(status: number, body?: unknown): void {
789824
fetchMock.mockResolvedValueOnce(

src/agents/auth-profiles/usage.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { normalizeProviderId } from "@openclaw/model-catalog-core/provider-id";
22
import type { OpenClawConfig } from "../../config/types.openclaw.js";
33
import { createSubsystemLogger } from "../../logging/subsystem.js";
44
import {
5+
asDateTimestampMs,
6+
isFutureDateTimestampMs,
57
positiveSecondsToSafeMilliseconds,
68
resolveExpiresAtMsFromEpochSeconds,
79
} from "../../shared/number-coercion.js";
@@ -812,8 +814,7 @@ export async function markAuthProfileBlockedUntil(params: {
812814
if (
813815
!profile ||
814816
isAuthCooldownBypassedForProvider(profile.provider) ||
815-
!Number.isFinite(blockedUntil) ||
816-
blockedUntil <= Date.now()
817+
!isFutureDateTimestampMs(blockedUntil)
817818
) {
818819
return;
819820
}
@@ -828,16 +829,16 @@ export async function markAuthProfileBlockedUntil(params: {
828829
if (!profile || isAuthCooldownBypassedForProvider(profile.provider)) {
829830
return false;
830831
}
831-
const now = Date.now();
832+
const now = asDateTimestampMs(Date.now());
833+
if (now === undefined) {
834+
return false;
835+
}
832836
previousStats = freshStore.usageStats?.[profileId];
833837
updateTime = now;
834838
const existingBlockedUntil = previousStats?.blockedUntil;
835-
const activeBlockedUntil =
836-
typeof existingBlockedUntil === "number" &&
837-
Number.isFinite(existingBlockedUntil) &&
838-
existingBlockedUntil > now
839-
? existingBlockedUntil
840-
: 0;
839+
const activeBlockedUntil = isFutureDateTimestampMs(existingBlockedUntil, { nowMs: now })
840+
? existingBlockedUntil
841+
: 0;
841842
nextStats = {
842843
...previousStats,
843844
blockedUntil: Math.max(activeBlockedUntil, blockedUntil),
@@ -876,15 +877,15 @@ export async function markAuthProfileBlockedUntil(params: {
876877
return;
877878
}
878879

879-
const now = Date.now();
880+
const now = asDateTimestampMs(Date.now());
881+
if (now === undefined) {
882+
return;
883+
}
880884
previousStats = store.usageStats?.[profileId];
881885
const existingBlockedUntil = previousStats?.blockedUntil;
882-
const activeBlockedUntil =
883-
typeof existingBlockedUntil === "number" &&
884-
Number.isFinite(existingBlockedUntil) &&
885-
existingBlockedUntil > now
886-
? existingBlockedUntil
887-
: 0;
886+
const activeBlockedUntil = isFutureDateTimestampMs(existingBlockedUntil, { nowMs: now })
887+
? existingBlockedUntil
888+
: 0;
888889
nextStats = {
889890
...previousStats,
890891
blockedUntil: Math.max(activeBlockedUntil, blockedUntil),

0 commit comments

Comments
 (0)