From 89283278a51d5d912bc711db4c74e99530038d93 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 31 Oct 2026 21:35:30 +0000 Subject: [PATCH 1/4] test(server): reproduce #28349 Claude subscriber limits reported as unsupported A Claude Max sign-in whose token lacks `user:profile` gets `rate_limits_available: false` with `subscription_type: "max"` from get_usage. It must map to `probeFailed`, not `unsupported`; API-key responses (`subscription_type: null`) stay `unsupported`. Co-Authored-By: Claude Opus 5.5 --- .../src/provider/claudeUsageLimits.test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/server/src/provider/claudeUsageLimits.test.ts b/apps/server/src/provider/claudeUsageLimits.test.ts index f20d8b0543636..8a43cdd8370c9 100644 --- a/apps/server/src/provider/claudeUsageLimits.test.ts +++ b/apps/server/src/provider/claudeUsageLimits.test.ts @@ -107,6 +107,22 @@ describe("claudeUsageResponseToLimits", () => { ]); }); + it("treats a subscription sign-in without readable limits as a failed probe, not a plan without limits", () => { + // Claude Max OAuth whose token lacks `user:profile`, as answered by Claude Code 2.1.316. + const subscriber = { + subscription_type: "max", + rate_limits_available: false, + rate_limits: null, + }; + expect( + claudeUsageResponseToLimits({ checkedAt, response: subscriber }).limits.unavailable?.reason, + ).toBe("probeFailed"); + const apiKey = { ...subscriber, subscription_type: null }; + expect( + claudeUsageResponseToLimits({ checkedAt, response: apiKey }).limits.unavailable?.reason, + ).toBe("unsupported"); + }); + const enterpriseResponse = { rate_limits_available: true, rate_limits: { From 6df6e2a6013a92b0c4ed4a23c35f9b0dfea9923c Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sat, 31 Oct 2026 21:45:40 +0000 Subject: [PATCH 2/4] fix(server): Claude subscription sign-ins without limit access no longer read as limitless MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude Code answers get_usage with `rate_limits_available: false` when a subscription token lacks the `user:profile` scope, while `subscription_type` still names the plan. That was mapped to `unsupported`, so `/usage-limits` said the Max plan had no subscription limits and Usage → Limits dropped Claude. Treat it as a failed probe with a message telling the user to run `claude auth login`; API-key, Bedrock and Vertex responses (`subscription_type: null`) stay `unsupported`. The capability probe now carries `subscription_type` through. Fixes pingdotgg/t3code#28349 Co-Authored-By: Claude Opus 5.5 --- apps/server/src/provider/ClaudeProvider.ts | 6 +++++- apps/server/src/provider/claudeUsageLimits.ts | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/apps/server/src/provider/ClaudeProvider.ts b/apps/server/src/provider/ClaudeProvider.ts index 95f5c6709a4ea..f1723382d9fa7 100644 --- a/apps/server/src/provider/ClaudeProvider.ts +++ b/apps/server/src/provider/ClaudeProvider.ts @@ -242,7 +242,10 @@ export type ClaudeCapabilitiesProbe = { readonly apiProvider: string | undefined; readonly slashCommands: ReadonlyArray; /** Undefined means get_usage failed; absent windows on success mean no subscription windows, as with API keys. */ - readonly usage?: Pick; + readonly usage?: Pick< + SDKControlGetUsageResponse, + "rate_limits_available" | "rate_limits" | "subscription_type" + >; readonly usageCheckedAt?: string; }; @@ -368,6 +371,7 @@ const probeClaudeCapabilities = ( ? { rate_limits_available: usageResult.success.rate_limits_available, rate_limits: usageResult.success.rate_limits, + subscription_type: usageResult.success.subscription_type, } : undefined; const account = init.account as diff --git a/apps/server/src/provider/claudeUsageLimits.ts b/apps/server/src/provider/claudeUsageLimits.ts index d0acf43bb4968..ce68d6b7aab77 100644 --- a/apps/server/src/provider/claudeUsageLimits.ts +++ b/apps/server/src/provider/claudeUsageLimits.ts @@ -173,10 +173,23 @@ export function claudeRateLimitEventToUpdate( /** get_usage percentages are already 0-100. */ export function claudeUsageResponseToLimits(input: { - readonly response: Pick; + readonly response: Pick & + Partial>; readonly checkedAt: string; }): { readonly limits: ServerProviderUsageLimits; readonly names: ClaudeScopedLimitNames } { const { response, checkedAt } = input; + if (!response.rate_limits_available && response.subscription_type) { + // The CLI withholds limits from subscription tokens that lack the `user:profile` scope. + return { + limits: makeUnavailableUsageLimits({ + checkedAt, + reason: "probeFailed", + message: + "Claude Code's sign-in can't read plan limits. Run `claude auth login` to sign in again.", + }), + names: { overageIncluded: undefined }, + }; + } if (!response.rate_limits_available || !response.rate_limits) { return { limits: makeUnavailableUsageLimits({ From 16f819385bb1c4f2b46426618a5a829da6e1c08d Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 10 Nov 2026 23:46:48 +0000 Subject: [PATCH 3/4] fix(server): point Claude re-login guidance at the T3 server and its Claude config AI-assisted; verified with the tests and tools listed above. --- apps/server/src/provider/claudeUsageLimits.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/provider/claudeUsageLimits.ts b/apps/server/src/provider/claudeUsageLimits.ts index ce68d6b7aab77..bbf8f1b2c95f1 100644 --- a/apps/server/src/provider/claudeUsageLimits.ts +++ b/apps/server/src/provider/claudeUsageLimits.ts @@ -185,7 +185,7 @@ export function claudeUsageResponseToLimits(input: { checkedAt, reason: "probeFailed", message: - "Claude Code's sign-in can't read plan limits. Run `claude auth login` to sign in again.", + "Claude Code's sign-in can't read plan limits. Run `claude auth login` on the machine running T3 Code, with the CLAUDE_CONFIG_DIR set in Claude's provider settings, to sign in again.", }), names: { overageIncluded: undefined }, }; From bd664c6e33e98f1488859e26ec3dbc40243baf0e Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 11 Nov 2026 00:48:38 +0000 Subject: [PATCH 4/4] fix(server): Claude limits guidance no longer assumes a custom config directory AI-assisted; verified with the tests and tools listed above. --- apps/server/src/provider/claudeUsageLimits.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/provider/claudeUsageLimits.ts b/apps/server/src/provider/claudeUsageLimits.ts index bbf8f1b2c95f1..1a2f896bf1e83 100644 --- a/apps/server/src/provider/claudeUsageLimits.ts +++ b/apps/server/src/provider/claudeUsageLimits.ts @@ -185,7 +185,7 @@ export function claudeUsageResponseToLimits(input: { checkedAt, reason: "probeFailed", message: - "Claude Code's sign-in can't read plan limits. Run `claude auth login` on the machine running T3 Code, with the CLAUDE_CONFIG_DIR set in Claude's provider settings, to sign in again.", + "Claude Code's sign-in can't read plan limits. Run `claude auth login` on the machine running T3 Code, using the Claude config directory from T3 Code's Claude provider settings if one is set, to sign in again.", }), names: { overageIncluded: undefined }, };