diff --git a/apps/server/src/provider/ProviderInstanceRegistry.test.ts b/apps/server/src/provider/ProviderInstanceRegistry.test.ts index 479ec05f0fb49..22757a1cc384c 100644 --- a/apps/server/src/provider/ProviderInstanceRegistry.test.ts +++ b/apps/server/src/provider/ProviderInstanceRegistry.test.ts @@ -650,7 +650,7 @@ describe("ProviderInstanceRegistry — multi-instance codex slice", () => { usageFailsAfterClaim: false, }); expect(outcome).toMatchObject({ _tag: "Success", success: "reset" }); - expect(after.usageLimits?.windows[0]?.usedPercent).toBe(0); + expect(after.usageLimits?.windows).toEqual([]); }), ); diff --git a/apps/server/src/provider/claudeUsageLimits.test.ts b/apps/server/src/provider/claudeUsageLimits.test.ts index a2da39b00d726..d41fb418d97c5 100644 --- a/apps/server/src/provider/claudeUsageLimits.test.ts +++ b/apps/server/src/provider/claudeUsageLimits.test.ts @@ -95,6 +95,42 @@ describe("claudeUsageResponseToLimits", () => { ).toEqual({ checkedAt, windows: [], unavailable: { reason: "unsupported" } }); }); + it("omits an unstarted session so an exhausted weekly allowance is the only row", () => { + const { limits } = claudeUsageResponseToLimits({ + checkedAt, + response: { + rate_limits_available: true, + rate_limits: { + five_hour: { utilization: 0, resets_at: null }, + seven_day: { utilization: 100, resets_at: "2026-07-24T08:59:00Z" }, + }, + }, + }); + expect(limits.windows).toEqual([ + { + id: "seven_day", + kind: "weekly", + label: "Weekly", + usedPercent: 100, + windowDurationMins: 10080, + resetsAt: "2026-07-24T08:59:00.000Z", + }, + ]); + }); + + it.each([ + { utilization: 0, resets_at: "2026-07-18T14:39:00Z" }, + { utilization: 54, resets_at: null }, + ])("keeps a session with usage or a reset time: %j", (five_hour) => { + const { limits } = claudeUsageResponseToLimits({ + checkedAt, + response: { rate_limits_available: true, rate_limits: { five_hour } }, + }); + expect(limits.windows).toMatchObject([ + { id: "five_hour", kind: "session", usedPercent: five_hour.utilization }, + ]); + }); + it("skips a window the endpoint reports without a utilization", () => { expect( claudeUsageResponseToLimits({ diff --git a/apps/server/src/provider/claudeUsageLimits.ts b/apps/server/src/provider/claudeUsageLimits.ts index a4dd6d0152be8..1a5cb62e7ddd7 100644 --- a/apps/server/src/provider/claudeUsageLimits.ts +++ b/apps/server/src/provider/claudeUsageLimits.ts @@ -150,6 +150,10 @@ export function claudeUsageResponseToLimits(input: { for (const id of Object.keys(WINDOWS)) { const window = response.rate_limits[id as "five_hour" | "seven_day"]; if (!window || typeof window.utilization !== "number") continue; + // The endpoint reports a session that never started as zero usage with + // no reset. Drawing it as "100% left" above an exhausted weekly row makes + // a blocked account look usable, so only started sessions get a row. + if (id === "five_hour" && window.utilization === 0 && window.resets_at === null) continue; windows.push(makeWindow(id, window.utilization, isoFromString(window.resets_at))); } // The CLI filters `model_scoped` to the overage-included allowlist, which