diff --git a/apps/server/src/provider/claudeUsageLimits.test.ts b/apps/server/src/provider/claudeUsageLimits.test.ts
index f20d8b0543636..b683c80f4f5e8 100644
--- a/apps/server/src/provider/claudeUsageLimits.test.ts
+++ b/apps/server/src/provider/claudeUsageLimits.test.ts
@@ -199,6 +199,22 @@ describe("claudeUsageResponseToLimits", () => {
).toEqual([monthlyWindow]);
});
+ it("flags a $0 cap instead of reporting a monthly window", () => {
+ const response = {
+ rate_limits_available: true,
+ rate_limits: {
+ five_hour: null,
+ seven_day: null,
+ extra_usage: { is_enabled: true, used_credits: 0, monthly_limit: 0, utilization: null },
+ },
+ };
+ expect(claudeUsageResponseToLimits({ checkedAt, response }).limits).toEqual({
+ checkedAt,
+ windows: [],
+ zeroSpendCap: true,
+ });
+ });
+
it("omits disabled budgets and refuses invalid money without an enabled fallback", () => {
const rateLimits = enterpriseResponse.rate_limits;
const response = {
diff --git a/apps/server/src/provider/claudeUsageLimits.ts b/apps/server/src/provider/claudeUsageLimits.ts
index d0acf43bb4968..8f81f2e7c7ef3 100644
--- a/apps/server/src/provider/claudeUsageLimits.ts
+++ b/apps/server/src/provider/claudeUsageLimits.ts
@@ -202,6 +202,7 @@ export function claudeUsageResponseToLimits(input: {
overageIncluded ??= entry.display_name;
}
const spendPercent = readSpendPercent(response.rate_limits);
+ const extra = response.rate_limits.extra_usage;
if (spendPercent !== undefined) {
windows.push({
id: "monthly_spend",
@@ -211,7 +212,10 @@ export function claudeUsageResponseToLimits(input: {
});
}
return {
- limits: makeUsageLimits({ checkedAt, windows }),
+ limits: {
+ ...makeUsageLimits({ checkedAt, windows }),
+ ...(extra?.is_enabled && extra.monthly_limit === 0 ? { zeroSpendCap: true } : {}),
+ },
names: { overageIncluded },
};
}
diff --git a/apps/web/src/components/usage/UsageLimitsPooled.tsx b/apps/web/src/components/usage/UsageLimitsPooled.tsx
index d5f46398fa31c..8ec1c7a02afcd 100644
--- a/apps/web/src/components/usage/UsageLimitsPooled.tsx
+++ b/apps/web/src/components/usage/UsageLimitsPooled.tsx
@@ -523,6 +523,18 @@ function PoolBar({
index={position + 1}
showAccountName={pool.columns.length > 1}
/>
+ ) : pool.kind === "monthly" && member.account.limits.zeroSpendCap ? (
+
) : null,
)}
diff --git a/packages/contracts/src/providerUsageLimits.ts b/packages/contracts/src/providerUsageLimits.ts
index 67acbe0d35b05..c3b871e996bf4 100644
--- a/packages/contracts/src/providerUsageLimits.ts
+++ b/packages/contracts/src/providerUsageLimits.ts
@@ -44,6 +44,8 @@ export type ServerProviderResetCredits = typeof ServerProviderResetCredits.Type;
export const ServerProviderUsageLimits = Schema.Struct({
checkedAt: IsoDateTime,
windows: ForwardCompatibleArray(ServerProviderUsageWindow),
+ /** A $0 monthly spend cap has no percentage, so it cannot be a window. */
+ zeroSpendCap: Schema.optional(Schema.Boolean),
credentialFingerprint: Schema.optional(TrimmedNonEmptyString),
resetCredits: Schema.optional(ServerProviderResetCredits),
externalUsage: Schema.optional(
diff --git a/packages/provider-core/src/server/usageLimits.test.ts b/packages/provider-core/src/server/usageLimits.test.ts
index 4b9c1b39c862b..6e2c1a18f0b62 100644
--- a/packages/provider-core/src/server/usageLimits.test.ts
+++ b/packages/provider-core/src/server/usageLimits.test.ts
@@ -66,10 +66,10 @@ describe("applyUsageLimitsUpdate", () => {
).toEqual({ checkedAt, windows: [weekly], unavailable: { reason: "probeFailed" } });
});
- it("preserves reset credits when a streamed window update changes usage", () => {
+ it("preserves reset credits and a $0 cap when a streamed window update changes usage", () => {
const resetCredits = { availableCount: 2, nextExpiresAt: "2026-10-01T00:00:00.000Z" };
const next = applyUsageLimitsUpdate({
- previous: { ...published, resetCredits },
+ previous: { ...published, resetCredits, zeroSpendCap: true },
checkedAt: "2026-09-03T12:00:05.000Z",
update: { windows: [{ ...session, usedPercent: 55 }] },
});
@@ -78,6 +78,7 @@ describe("applyUsageLimitsUpdate", () => {
checkedAt: "2026-09-03T12:00:05.000Z",
windows: [{ ...session, usedPercent: 55 }, weekly],
resetCredits,
+ zeroSpendCap: true,
});
});
});
diff --git a/packages/provider-core/src/server/usageLimits.ts b/packages/provider-core/src/server/usageLimits.ts
index 1e4bcd0090854..b8ff4edff10f7 100644
--- a/packages/provider-core/src/server/usageLimits.ts
+++ b/packages/provider-core/src/server/usageLimits.ts
@@ -84,6 +84,7 @@ export function applyUsageLimitsUpdate(input: {
return {
...makeUsageLimits({ checkedAt: input.checkedAt, windows: merged.values() }),
...(previous?.resetCredits !== undefined ? { resetCredits: previous.resetCredits } : {}),
+ ...(previous?.zeroSpendCap ? { zeroSpendCap: true } : {}),
...(unavailable !== undefined ? { unavailable } : {}),
};
}