Repository navigation
feat(usage): Block Reset Timer can hide until the 5-hour block hits 100% - #697
Merged
sirmalloc merged 12 commits intoOct 26, 2026
Merged
Conversation
…not a fixed sample
…ge API The under-limit hide state read the session percent from the status payload's rate_limits whenever it had one. That percent comes from the session's own last API response, so it stops moving while the session sits idle: with several sessions open, one left idle while another used up the 5-hour limit kept its timer hidden at the limit. After a reset it could also keep showing a timer from the old window's 100%. The state now fetches the usage API's percent even when the payload has one, and keeps it apart from the merged data as apiSessionUsage, so Session Usage and every other widget still show the payload's percent. The API's answer is the account's, cached for 3 minutes and shared by every session, so this costs at most one request per 3 minutes. When the fetch fails, the payload's percent stands in, without an error.
The under-limit state's checklist label read "when session usage is under 100%", but ccstatusline also says "session" for the Claude Code conversation (Session Clock, Session Cost), and the timers for the 5-hour window say "block". The label now reads "when the 5-hour block is under 100%", and the docs, comments and test names say the block's percent too. "Session" stays where it means a Claude Code session, such as a session left idle. Code names that follow the usage data's `sessionUsage` field keep it.
Another change renames Session Usage to Block Usage. Say "usage percent" where the under-limit state's docs and two test comments named it, so they read right with either name.
The usage API's answer is cached for 3 minutes, and kept for as long as the API keeps failing. Across a reset, under-limit took that answer's 100% for the new block's, so the reset countdown showed at the start of a fresh block (at 40%, say) until a fetch succeeded. The API's percent now counts only while its block hasn't reset; otherwise the payload's percent stands in, as when the fetch fails.
While the usage API can't be reached (a 429 backoff, a failed request, no credentials), fetchUsageData serves the last good cache however old it is, and nothing tells it apart from a fresh answer: unlike a failed fetch it carries no error. Block Reset Timer's under-limit state took that cache's 5-hour percent as the API's current answer, so the payload's percent never stood in. Through a backoff, which a Retry-After can stretch to an hour, a cache written at 97% kept the reset countdown hidden after the payload had reached 100% in the same block. fetchUsageData now marks the stale fallback it returns, including when a later call serves it again from memory. The prefetch takes the block's API percent only from the API or a current cache, so a stale fallback counts as the API being unreachable, like a failed fetch, and the payload's percent decides, as the under-limit state's docs describe. Only a cache that has outlived a refresh is marked: one older than the 3-minute cache lifetime plus the 30-second lock a fetch holds while in flight. Another session's ordinary refresh serves the cache through that lock too, seconds past its 3 minutes and as current as the shared cache ever is, and marking it would let an idle session's frozen payload percent override the API's for that render.
The under-limit hide state preferred apiSessionUsage and fell back to the merged sessionUsage field. When the status payload omitted its percentage, that merged field retained the API value even if prefetch rejected it as stale or from a block that had already reset. An old cached 100% could therefore show a new block's countdown from a reset-only payload. Read the fallback percentage directly from the original status payload in the render context. Continue preferring the API percentage accepted by prefetch, and treat missing or null payload percentages as unknown so the timer stays hidden. Keep general usage merging and other widgets unchanged. Add prefetch-to-render regression coverage for expired and stale cached 100% readings with missing, null, zero, 97%, and 100% payload percentages, plus an entirely absent payload. Verify that merged usage is preserved while rejected API values cannot decide visibility. Update widget fixtures to supply their fallback percentages through the original payload. Validation: - Both new regression cases reproduced the erroneous countdown before the fix. - Targeted prefetch and Block Reset Timer tests: 96 passed. - bun run lint: passed (TypeScript and ESLint). - bun test: 3,191 passed; two local proxy listener tests failed because the sandbox blocked binding to 127.0.0.1. Both passed outside the sandbox (the status-service file: 30 passed; the usage proxy timeout test: 1 passed).
Bring in main through cf73531, including the dependency updates from sirmalloc#745. The merge applies cleanly and preserves the Block Reset Timer under-limit feature and the fix that keeps rejected API percentages out of its fallback. Install the merged dependency versions with bun install --frozen-lockfile. No conflict resolutions or additional source changes were required. Validation: - bun run lint: passed (TypeScript and ESLint). - bun test: 3,193 passed, 0 failed across 178 files, with local proxy listeners permitted so the proxy timeout tests execute successfully. - git diff --check and git diff --cached --check: passed.
eric-engberg
added a commit
to eric-engberg/ccstatusline
that referenced
this pull request
Oct 27, 2026
Main's stale-cache check from sirmalloc#697 (hasUsageCacheOutlivedRefresh) reads the usage cache through getCacheFile(), this branch's cache-dir fallback. A usage error's errorTransient and sirmalloc#697's apiSessionUsage are both left out of UsageDataField, and each side's prefetch tests stay.
axisrow
added a commit
to axisrow/ccstatusline
that referenced
this pull request
Oct 27, 2026
…it names, sirmalloc#717 claude-settings test isolation, sirmalloc#705 jj detached HEAD, sirmalloc#703 hooks save warning, sirmalloc#700 dev reload, sirmalloc#698 renderer merge ends at hidden widget, sirmalloc#697 block reset timer under-limit hide, sirmalloc#745 dev-deps bumps Conflict policy: upstream infra wins, fork features ported on top (fork-only src/daemon/, src/widgets/editors/*, parity fixtures and fork widgets kept). Notable joins: sirmalloc#703 returns SkippedWidgetHooks through the fork's saveSettingsTo wrapper; sirmalloc#697 keeps the fork's zero hide state alongside upstream's under-limit state; sirmalloc#698 replaces the fork's merge-glue palette semantics with upstream's merge-ends-at-unmerged-empty-widget rule; dev-reload (ctrl+r) composes with the fork's first-run preset picker. Co-Authored-By: Claude Code <noreply@anthropic.com>
pcvelz
added a commit
to pcvelz/ccstatusline-usage
that referenced
this pull request
Oct 29, 2026
Upstream: Extra Usage Limit widget (sirmalloc#756), live widget preview while picking (sirmalloc#755), Fast Mode widget (sirmalloc#754), transcript block fallback fixes (sirmalloc#752), editor keys on a global install (sirmalloc#751), git cache TTL (sirmalloc#750), npx cached copy skip (sirmalloc#749), Block Usage hide at 100% (sirmalloc#747), Block Reset Timer hide until 100% (sirmalloc#697), fish-style branch abbreviation (sirmalloc#614), jj detached HEAD (sirmalloc#705), hooks save warning (sirmalloc#703), dev reload (sirmalloc#700), merge ends at a hidden widget (sirmalloc#698), Block Forecast and Block Limit Timer widgets (sirmalloc#699), contributor guide (sirmalloc#706), POSIX path case fix (sirmalloc#707), whitespace-distinct conflict filenames (sirmalloc#709), Quick Start reorder (sirmalloc#715), bunx launch guidance (sirmalloc#714), jj diff summary counts (sirmalloc#711), powerline manual separators (sirmalloc#713), keychain credential by account (sirmalloc#532), Keychain probe timeout (sirmalloc#760), usage cache scoped per CLAUDE_CONFIG_DIR (sirmalloc#266), test isolation (sirmalloc#717, sirmalloc#753), dev-dependency bumps (sirmalloc#745) Conflicts resolved per the standard-resolution table; fork deletions re-applied. Hand edits outside conflicts: - src/utils/global-command-resolution.ts: fork-rename (ccstatusline@ -> ccstatusline-usage@ in upstream's new bunx pattern) - src/utils/__tests__/global-package-manager.test.ts: fork-rename (package dir ccstatusline-usage in upstream's new tests) - src/utils/usage-fetch.ts: compat fix - cacheUsageData gained a fetchedAtMs arg (upstream sirmalloc#266); adapted the fork-only fetchFreshUsageData call site - src/utils/__tests__/usage-fetch.test.ts: compat fixes - cache/lock now hashed per CLAUDE_CONFIG_DIR (usageFilePath helper) in four fork tests; upstream fableUsage renamed to fork's weeklyFableUsage; seedUsageCache takes TokenHome - src/utils/__tests__/usage-prefetch.test.ts: compat fix - mockFetchUsageData renamed to upstream's mockFetchUsage in cleanly-merged tests - README.md: took upstream's Quick Start move (sirmalloc#715) and dropped the fork's old copy; fork-rename of its ccstatusline@ specifiers; links to fork-deleted docs (docs/USAGE.md, docs/WINDOWS.md) replaced with the fork's inline text - CONTRIBUTING.md: links to the fork-deleted docs/DEVELOPMENT.md pointed at the README Development section - docs/test-retirements.md: ledger entries for the 12 upstream test renames (sirmalloc#697, sirmalloc#698, sirmalloc#700, sirmalloc#705, sirmalloc#713, sirmalloc#760)
eric-engberg
added a commit
to eric-engberg/ccstatusline
that referenced
this pull request
Oct 30, 2026
…build The branch was rebuilt on today's updated #115 and #80, which hold upstream main; #71 and #89, which it also merged, are upstream (sirmalloc#697, sirmalloc#747) and in #115 through main. This keeps the old history so the push is a fast-forward; the tree is the rebuild's.
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
Block Reset Timer's
(h)ide…list has a new entry, "when the 5-hour block is under 100%" (under-limit). With it checked, the timer stays out of the status line until the 5-hour limit is hit, then shows the usualReset: 1hr 43mso you know how long until you can work again.Why
Until you hit the 5-hour limit, the time until the block resets doesn't change what you do, but it takes space on every line. Once you're at 100% it's the one number that matters. Today the timer is all or nothing; this lets it appear only when it's useful.
How
sessionUsage >= 100, not when Block Usage's rounded text reads100%. With whole numbers, 99.7% reads as100%but you can still work, so the timer stays hidden.rate_limitsand no usage data) means the timer stays hidden, rather than showing a countdown it can't justify. A usage error is hidden the same way.sessionUsageonly for timers that have the state checked, the same way Block Usage's time cursor asks for the reset time. It fetches it from the usage API even when the status payload'srate_limitshas one. The payload's percent comes from that session's last API response, so it stops moving while the session sits idle. With several sessions open, one left idle while another used up the limit would keep its timer hidden at the limit. After a reset it could also show a timer from the old block's 100%.apiSessionUsage), so Block Usage and every other widget still show the payload's percent as before.Reset: 4hr 30m), since the widget returns the preview before any hide check, as the existingno-datastate does. With the state off, nothing changes. Combined withno-data, a timer at 100% with no reset window yet stays hidden instead of showing[Loading].Update: Don't let a stale usage cache decide the under-limit state
fetchUsageDataserves the last good cache however old it is, with no error set, so nothing told it apart from a fresh answer. The prefetch took that cache's 5-hour percent as the API's current answer, so the payload's percent never stood in. During a backoff, which a Retry-After can stretch to an hour, a cache written at 97% kept the reset countdown hidden after the payload had reached 100% in the same block.fetchUsageDatanow marks the stale fallback it returns, including when a later call serves it again from memory. It keeps aWeakSetand exportsisStaleUsageFallback. The prefetch doesn't take the block's API percent from a marked result. A stale fallback therefore counts as the API being unreachable, like a failed fetch, and the payload's percent decides, as the under-limit docs describe.statSyncof the cache file in a small helper next toreadStaleUsageCache, and it counts a failed stat as outlived.Demo
A fake status payload with the 5-hour block at 60% and then 100%, with the reset 1hr 43m away. Before, the timer shows at both; after, only at 100%. The recording has no usage API credentials, so the payload's percent stands in, which is what the API would report at these percents. Reading the API changes which percent decides, not how the widget looks, so the media only changed for the new wording.
Powerline, before and after:
Plain, before and after:
A new block at 40% in the payload while the usage API cache (fake login and data) still holds the last block's answer: 100%, with a reset 20 minutes ago, as when the API kept failing across the reset. Before, the countdown shows at 40%; after, the cached answer is ignored and the timer stays hidden.
Powerline, before and after:
Plain, before and after:
Turning it on in the line editor: select the timer,
h, check the new entry, Enter.Powerline
Plain
Update
Block Usage and Block Reset Timer (hidden 'under-limit'), piped with fake data while the usage API is in a 429 backoff for the next hour. Its last cached answer is 40 minutes old and for the current 5-hour block, and nothing is fetched. Before, the stale cache decided: at 100% the countdown stayed hidden because the cache said 97%, and at 97% it showed because the cache said 100%. After, the payload's percent decides both ways, so the countdown shows at 100% and is hidden at 97%.
Powerline, before and after:
Plain, before and after:
Testing
[Loading]at 100% with no reset window whenno-datais also on; the declared states.rate_limitshas one, and kept apart so Block Usage keeps the payload's; a failed fetch for it alone is suppressed, with the payload's percent standing in.bun test: 2794 pass, 0 fail.bun run lintpasses. The changed test files never write toHOME(run withHOMEpointed at an empty directory).main).main(the timer is hidden); at 100% it matchesmain. The TUI opens and exits cleanly under both.main:bun test3067 pass, 0 fail,bun run lintpasses, and the built CLI's plain and Powerline renders matchmainunder Bun 1.4.2 and Node 26.10.0.bun test3070 pass, 0 fail;bun run lintpasses; under Node (Vitest) both changed test files pass, 84 of 84. Built CLI under Bun 1.4.2 and Node 26.10.0 with payloads at 97% and 100% and no credentials: identical across runtimes and to the previous commit in plain and Powerline modes. Against the live API, with a made-up payload at 100% while the account was at 12%: the timer stays hidden, where the previous commit showedReset: 1hr 42m.bun test3070 pass, 0 fail;bun run lintpasses; under Node (Vitest) both changed test files pass, 84 of 84. The media is re-recorded for the new label.bun test3185 pass, 0 fail;bun run lintpasses; under Node (Vitest) the prefetch test file passes, 54 of 54. Built CLI under Bun 1.4.2 and Node 26.10.0, plain and Powerline, from a scratchHOMEwhose fresh cache holds the old block's 100%: before,5h: 40.0% | Reset: 4hr 39m; after,5h: 40.0%, identical across runtimes.Update:
usage-fetch.test.ts: a newit.eachcase covers a render that lands while another render's refresh is in flight (atimeoutlock with 28s left). A cache 185s old is not marked as a stale fallback; before the threshold this case failed with[true, true]against[false, false]. A cache 215s old is marked, which pins the other side of the threshold. The two cases that check a stale cache served through a backoff and through a failed request now use a 40-minute-old cache.usage-prefetch.test.ts: the test 'falls back to rate_limits' block percent when the under-limit fetch only has a stale cache' now ties the stale mark to the exact objectfetchUsageDatareturned. If the check reads the wrong variable it fails, receivingapiSessionUsage97, and it also fails if the check is removed. The old mock passed in both cases.bun test: 3191 pass, 0 fail, across 178 files (3189 before, plus the 2 new cases).bun run lint(tsc and ESLint) passes.usage-prefetch.test.tspasses 55/55.usage-fetch.test.tscan't run its probe subprocess under plain Node, because Node can't resolveusage.ts's extensionless imports: 46 cases fail withERR_MODULE_NOT_FOUND, the 44 known before plus the 2 new probe cases. To cover it on Node, I built the dist and ran it under Node 26.10.0 and Bun 1.4.2 in 9 scenarios: a cache hit after a reset, a stale fallback after a reset, a 429 backoff in both directions, a control, and an in-flight refresh at 185s and 215s in both directions. Each ran from a scratch HOME with fake credentials and a deadHTTPS_PROXY. Both runtimes gave the same output, and the cache and lock files were never touched.5h: 100.0%to5h: 100.0% | Reset: 1hr 59m. During another session's refresh (cache 185s old at 100%, payload 97%), it stays5h: 97.0% | Reset: 1hr 59m, as before.