Repository navigation
fix(usage): refetch a cached usage answer from before a 5-hour or weekly reset - #814
Open
eric-engberg wants to merge 7 commits into
Open
eric-engberg wants to merge 7 commits into
eric-engberg wants to merge 7 commits into
Conversation
…kly reset Values the statusline payload doesn't carry, such as Weekly Fable Usage and the extra usage figures, come only from the usage API, whose answer is cached for 180 seconds and shared by every session. When the 5-hour or weekly window reset, Claude Code's payload already showed the new window while the cached answer still described the old one. Fable Weekly Usage then kept the previous week's percentage until the cache expired, up to three minutes after the reset. prefetchUsageDataIfNeeded now passes the payload's five_hour and seven_day reset ends to fetchUsageData. A memory or file cache that ends either window more than 60 seconds before the payload does was written before that reset, so it no longer counts as fresh and is fetched again. The two sources give the same instant to within a second, while a reset moves the end by 5 hours or 7 days. Only a later payload end counts. A session idle since before the reset keeps showing the window of its last request, and must not refetch an answer another session has already brought up to date. Percentages aren't compared for the same reason: an idle session's stay where they were. The refetch keeps the existing guards. Under an active usage lock (a 429 backoff, another render's fetch in flight, a parse error) or the no-credentials backoff, the pre-reset answer is served as the stale fallback, as before. A response that still ends the window before the payload does is cached but keeps the 30-second in-flight lock, so an API that lags the payload is asked at most every 30 seconds rather than on every render. Once a response matches the payload, later renders serve it from the cache. A reset of a window the payload doesn't carry, such as a Fable window ending at a different time from the weekly one, can't be seen this way and still waits for the cache to expire.
Upstream sirmalloc#697 and sirmalloc#747 (the reset timer's and Block Usage's at-limit hide states) fetch the block's percent; their prefetch tests now expect the payload's reset end this branch passes along, as fork #117 did. The branch's own cache tests use upstream sirmalloc#266's per-profile file names.
# Conflicts: # docs/DEVELOPMENT.md
…mer's prefetch calls This branch passes the statusline payload's 5-hour and weekly reset ends to fetchUsageData, so sirmalloc#768's two prefetch tests for Weekly Fable Limit Timer, which check the exact fetchUsageData calls, expect that option too.
# Conflicts: # docs/USAGE.md # src/utils/__tests__/usage-fetch.test.ts # src/utils/usage-prefetch.ts # src/utils/usage.ts
…ir reset Since the stale fallback leaves out windows whose own reset time has passed, the pre-reset answer a blocked refetch falls back to no longer shows such a window.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Right after a 5-hour or weekly reset, the usage widgets no longer show the previous window's numbers for up to three minutes. When Claude Code's status line data already shows a new window but the cached usage API answer is from before the reset, the next render fetches a new answer, once. Weekly Fable Usage and the other values that come only from the usage API then match the new window right away.
Why
The status line payload carries the 5-hour and weekly percentages and reset times (
rate_limits.five_hour/seven_day). Weekly Fable Usage, the extra usage figures, and anything else the payload doesn't carry come only from the usage API. That answer is cached inusage.jsonfor 180 seconds and shared by every session. The freshness check looked only at the answer's age, its login and which fields it held. So an answer cached a minute before a reset still counted as fresh after it, while the payload on the same line had already moved to the new window.Reproduced with a fake login and a fake usage API (
bun --preload/node --require, nothing fetched). The week reset 30 seconds ago, so the payload ends the week 7 days later, at 1% used.usage.json, written a minute earlier, before the reset, ends the week at the reset with Fable at 88%. The fake API now answers 3%. Builtdist/onmain, piped twice, under Bun 1.4.2 and Node 26.10.0 (same output on both; the request count comes from the fake API's log):The line contradicts itself: the week has restarted, but Fable shows last week's number, and it keeps doing so until the cache file is 180 seconds old. In use, two sessions showed the old Fable number for about 26 and 90 seconds after a weekly reset.
How
prefetchUsageDataIfNeededpasses the payload's 5-hour and weekly reset ends tofetchUsageDataaspayloadResetAt, whenever the payload has at least one.fetchUsageDatacounts a memory or file cache as fresh only while neither window's payload end is more than 60 seconds later than the cached answer's. Both sources report the same instant: the payload in whole seconds, the API with fractions of a second. A reset moves the end by the window's whole length, 5 hours or 7 days.main. Since fix(usage): drop stale cached windows that have already reset #736, that fallback leaves out a window whose own reset time has passed.docs/USAGE.md(usage cache) anddocs/DEVELOPMENT.md(cache mechanics) describe the refetch and its limits, including that the stale fallback a blocked refetch serves leaves out windows past their reset.Left as is:
weekly_scopedentry resets together withweekly_all, so a weekly reset covers it.Demo
Piped renders with a fake login and a fake usage API, nothing fetched: the scenario from Why, rendered twice, two seconds apart. Before: Weekly Fable shows 88.0% on both renders with no request. After: the first render fetches once and shows 3.0%, and the second serves that from the cache without another request.
Powerline
Before:
After:
Plain
Before:
After:
Testing
mainas of 2.2.34 (0c94df7): it merges after conflicts next to fix(usage): show a newer usage API percent in a session left idle #767's and feat: add an Extra Usage Today widget #811's imports, test probe and docs, resolved by keeping both.bun run lintis clean, andbun teston Linux CI gives 4414 pass, 0 fail (4403 onmain). The built CLI under Bun 1.4.4 and Node 26.13.0 agrees across runtimes and matchesmainin plain and Powerline mode, and the TUI opens and exits cleanly under both. It adds no JavaScript or Node API newer than Node 14, the build's target.usage-prefetch.test.tsassertions that now expect the reset ends in the fetch call. Run againstmain's code, 9 fail: the 2 refetch tests, the 30-second lock test, the new prefetch test and the 5 updated assertions. The other 7 new cases pass onmaintoo, becausemainnever refetches a fresh cache. They pin what this branch must keep: no refetch when the reset ends match, when the payload ends the week at most a minute later or a week earlier, or when the cached answer has no end for that window; and none during a rate-limit backoff, while another render's fetch is in flight, or without a login.usage-fetch.test.ts(10 tests, through the existing subprocess probe with a stubbed API):usage-prefetch.test.ts(1 test): the payload's reset ends reach the fetch when Weekly Fable Usage needs the API.usage-fetch.test.tsandusage-prefetch.test.tsundernode node_modules/vitest/vitest.mjs runon Node 26.10.0. 72 of 123 pass, against 71 of 112 onmain. Every failure is a probe test whose spawned Node process can't load the.tssources (ERR_MODULE_NOT_FOUND): the same 41 as onmain, plus the 10 new probe tests, which use the same probe. The new prefetch test passes under Node.runtime-check.shon Bun 1.4.2 and Node 26.10.0:mainin plain and Powerline mode, with both runtimes agreeing. The TUI opens and exits cleanly on both.mainon both runtimes: the refetch fails and the cached answer stays.dist/under Bun 1.4.2 and Node 26.10.0, plain and Powerline, with the fake API, two renders each:main:Weekly Fable: 88.0%with no request.Weekly Fable: 3.0%after one request, with no request on the second render.