From 7102af10b43dbf5fe3c716b8754985ee1de122c3 Mon Sep 17 00:00:00 2001 From: Hannes Lenz Date: Fri, 30 Oct 2026 13:27:48 +0100 Subject: [PATCH] fix(server): a rate-limited Claude usage read no longer clears the limit bars --- apps/server/src/provider/ClaudeProvider.ts | 2 +- .../src/provider/claudeUsageLimits.test.ts | 19 +++++++++++++++++++ apps/server/src/provider/claudeUsageLimits.ts | 3 ++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/apps/server/src/provider/ClaudeProvider.ts b/apps/server/src/provider/ClaudeProvider.ts index 5bc82a41d4eda..bb0ebf5738e5d 100644 --- a/apps/server/src/provider/ClaudeProvider.ts +++ b/apps/server/src/provider/ClaudeProvider.ts @@ -243,7 +243,7 @@ export type ClaudeCapabilitiesProbe = { /** OAuth applies only to firstParty; Bedrock uses external AWS credentials and omits subscription/token fields. */ readonly apiProvider: string | undefined; readonly slashCommands: ReadonlyArray; - /** Undefined means get_usage failed; absent windows on success mean no subscription windows, as with API keys. */ + /** Undefined or `rate_limits.error` means get_usage failed; absent windows on success mean no subscription windows, as with API keys. */ readonly usage?: Pick; readonly usageCheckedAt?: string; }; diff --git a/apps/server/src/provider/claudeUsageLimits.test.ts b/apps/server/src/provider/claudeUsageLimits.test.ts index f20d8b0543636..92ad088a17731 100644 --- a/apps/server/src/provider/claudeUsageLimits.test.ts +++ b/apps/server/src/provider/claudeUsageLimits.test.ts @@ -107,6 +107,25 @@ describe("claudeUsageResponseToLimits", () => { ]); }); + it("treats a refused usage read as a failed probe, not as zero windows", () => { + expect( + claudeUsageResponseToLimits({ + checkedAt, + response: { + rate_limits_available: true, + rate_limits: { + ...({ + error: { type: "rate_limit_error", message: "Rate limited. Please try again later." }, + } as object), + }, + }, + }), + ).toEqual({ + names: noNames, + limits: { checkedAt, windows: [], unavailable: { reason: "probeFailed" } }, + }); + }); + const enterpriseResponse = { rate_limits_available: true, rate_limits: { diff --git a/apps/server/src/provider/claudeUsageLimits.ts b/apps/server/src/provider/claudeUsageLimits.ts index d0acf43bb4968..6e029ec1c1254 100644 --- a/apps/server/src/provider/claudeUsageLimits.ts +++ b/apps/server/src/provider/claudeUsageLimits.ts @@ -177,7 +177,8 @@ export function claudeUsageResponseToLimits(input: { readonly checkedAt: string; }): { readonly limits: ServerProviderUsageLimits; readonly names: ClaudeScopedLimitNames } { const { response, checkedAt } = input; - if (!response.rate_limits_available || !response.rate_limits) { + // The SDK does not type it, but a refused usage read (e.g. HTTP 429) arrives as `rate_limits: { error }`. + if (!response.rate_limits_available || !response.rate_limits || "error" in response.rate_limits) { return { limits: makeUnavailableUsageLimits({ checkedAt,