From 22634693427c456ab408028e52212284cd8c2285 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Thu, 2 Jul 2026 08:30:36 +0200 Subject: [PATCH] fix(server): show Claude usage when a quota window has no scheduled reset (#1855) The Anthropic usage API returns resets_at: null when a window has no active session. The schema only allowed string/undefined, so the parse threw and the whole Claude provider surfaced as an error in the quota panel. The consuming code already handled null; only the schema was stricter than the API. --- .../quota-fetcher/providers/claude.ts | 2 +- .../services/quota-fetcher/service.test.ts | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/server/src/services/quota-fetcher/providers/claude.ts b/packages/server/src/services/quota-fetcher/providers/claude.ts index caa7f967f..05624d481 100644 --- a/packages/server/src/services/quota-fetcher/providers/claude.ts +++ b/packages/server/src/services/quota-fetcher/providers/claude.ts @@ -37,7 +37,7 @@ const ClaudeCredentialsSchema = z.object({ const ClaudeUsageWindowSchema = z.object({ utilization: ApiNumberSchema, - resets_at: z.string().optional(), + resets_at: z.string().nullish(), }); const ClaudeUsageResponseSchema = z.object({ diff --git a/packages/server/src/services/quota-fetcher/service.test.ts b/packages/server/src/services/quota-fetcher/service.test.ts index 5c23d4237..71f371c34 100644 --- a/packages/server/src/services/quota-fetcher/service.test.ts +++ b/packages/server/src/services/quota-fetcher/service.test.ts @@ -434,6 +434,33 @@ describe("real provider usage fetchers", () => { ); }); + it("accepts a null Claude resets_at when a window has no scheduled reset", async () => { + writeClaudeCredentials(claudeHome, "at_valid"); + fetchApi = mockFetch( + new Map([ + [ + "https://api.anthropic.com/api/oauth/usage", + () => + jsonResponse({ + five_hour: { utilization: 0, resets_at: null }, + seven_day: { utilization: 1, resets_at: "2026-06-04T00:00:00Z" }, + }), + ], + ]), + ); + + const result = await service().listUsage(); + const claude = findProvider(result, "claude"); + + expect(claude).toMatchObject({ + status: "available", + windows: expect.arrayContaining([ + expect.objectContaining({ id: "five_hour", usedPct: 0, resetsAt: null }), + expect.objectContaining({ id: "weekly", usedPct: 1 }), + ]), + }); + }); + it("returns unavailable Claude usage when credentials are missing", async () => { fetchApi = vi.fn() as never;