Skip to content

Commit 2bb0cdf

Browse files
zachthedevclaude
andauthored
fix: stop refetching usage every render on accounts without rate-limit windows (#434)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent c04247f commit 2bb0cdf

2 files changed

Lines changed: 79 additions & 0 deletions

File tree

src/utils/__tests__/usage-fetch.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,23 @@ describe('fetchUsageData error handling', () => {
357357
disabled_reason: null
358358
}
359359
});
360+
// Mirrors a real Enterprise-account response: every rate-limit window is
361+
// null because Enterprise plans have no 5-hour/7-day windows (#343).
362+
const enterpriseNullWindowsResponseBody = JSON.stringify({
363+
five_hour: null,
364+
seven_day: null,
365+
seven_day_oauth_apps: null,
366+
seven_day_sonnet: null,
367+
seven_day_opus: null,
368+
extra_usage: {
369+
is_enabled: true,
370+
monthly_limit: 50000,
371+
used_credits: 0,
372+
utilization: null,
373+
currency: 'USD',
374+
disabled_reason: null
375+
}
376+
});
360377
const rateLimitedResponseBody = JSON.stringify({
361378
error: {
362379
message: 'Rate limited. Please try again later.',
@@ -717,6 +734,52 @@ describe('fetchUsageData error handling', () => {
717734
}
718735
});
719736

737+
it('treats null rate-limit windows as complete for window reset fields', () => {
738+
const harness = createProbeHarness();
739+
740+
try {
741+
const home = harness.createTokenHome('enterprise-null-windows');
742+
const requiredFields = ['sessionUsage', 'sessionResetAt', 'weeklyUsage', 'weeklyResetAt', 'weeklySonnetResetAt', 'weeklyOpusResetAt'];
743+
const result = harness.runProbe({
744+
claudeConfigDir: home.claudeConfig,
745+
home: home.home,
746+
mode: 'success',
747+
nowMs,
748+
pathDir: home.bin,
749+
requiredFields,
750+
responseBody: enterpriseNullWindowsResponseBody
751+
});
752+
753+
expect(result.first).toEqual({
754+
sessionUsage: 0,
755+
weeklyUsage: 0,
756+
weeklySonnetUsage: 0,
757+
weeklyOpusUsage: 0,
758+
extraUsageEnabled: true,
759+
extraUsageLimit: 50000,
760+
extraUsageCurrency: 'USD',
761+
extraUsageUsed: 0
762+
});
763+
expect(result.second).toEqual(result.first);
764+
expect(result.requestCount).toBe(1);
765+
766+
const cachedResult = harness.runProbe({
767+
claudeConfigDir: home.claudeConfig,
768+
home: home.home,
769+
mode: 'unexpected',
770+
nowMs: nowMs + 10000,
771+
pathDir: home.bin,
772+
requiredFields
773+
});
774+
775+
expect(cachedResult.first).toEqual(result.first);
776+
expect(cachedResult.second).toEqual(result.first);
777+
expect(cachedResult.requestCount).toBe(0);
778+
} finally {
779+
harness.cleanup();
780+
}
781+
});
782+
720783
it('keeps parse-error locks distinct from timeout locks', () => {
721784
const harness = createProbeHarness();
722785

src/utils/usage-fetch.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ const EXTRA_USAGE_DETAIL_FIELDS = new Set<UsageDataField>([
3333
'extraUsageUtilization'
3434
]);
3535

36+
// Maps each window reset field to the utilization field parsed from the same
37+
// API bucket. A null bucket (Enterprise accounts have no rate-limit windows,
38+
// #343) parses to utilization 0 with no resets_at, so once the utilization is
39+
// cached the missing timestamp is conclusive and refetching cannot produce it.
40+
const WINDOW_RESET_FIELD_SENTINELS: Partial<Record<UsageDataField, UsageDataField>> = {
41+
sessionResetAt: 'sessionUsage',
42+
weeklyResetAt: 'weeklyUsage',
43+
weeklySonnetResetAt: 'weeklySonnetUsage',
44+
weeklyOpusResetAt: 'weeklyOpusUsage'
45+
};
46+
3647
const UsageCredentialsSchema = z.object({ claudeAiOauth: z.object({ accessToken: z.string().nullable().optional() }).optional() });
3748
const UsageLockErrorSchema = z.enum(['timeout', 'rate-limited', 'parse-error']);
3849
const UsageLockSchema = z.object({
@@ -185,6 +196,11 @@ function hasRequiredUsageField(data: UsageData, field: UsageDataField): boolean
185196
return true;
186197
}
187198

199+
const windowSentinel = WINDOW_RESET_FIELD_SENTINELS[field];
200+
if (windowSentinel !== undefined && data[windowSentinel] !== undefined) {
201+
return true;
202+
}
203+
188204
// Once the API has reported the extra usage state, missing detail fields are
189205
// conclusive: accounts without a configured monthly limit never report
190206
// monthly_limit/utilization, so refetching cannot produce them (#413).

0 commit comments

Comments
 (0)