Skip to content

feat(segments): usage-session and usage-weekly from the local accounts cache - #18

Merged
andrei-hasna merged 2 commits into
mainfrom
fix/usage-segments
Aug 7, 2026
Merged

feat(segments): usage-session and usage-weekly from the local accounts cache#18
andrei-hasna merged 2 commits into
mainfrom
fix/usage-segments

Conversation

@andrei-hasna

@andrei-hasna andrei-hasna commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

What

Adds two opt-in statusline segments that show usage remaining for the account this pane is actually running as:

  • usage-session — the ~5-hour session (hourly) window, e.g. 5h 87%
  • usage-weekly — the weekly window (the binding limit), e.g. 7d 92%

Owner request 2026-08-07: show hourly (session) and weekly usage left for the pane's own account.

How it reads the cache without the cloud API

statusline runs inside a launched, registry-stripped session, so it must not call the accounts cloud API. Instead:

  1. The pane's account uuid is read from oauthAccount.accountUuid in the live CLAUDE_CONFIG_DIR state file — the same per-process signal auth-profile/auth-email already use (0.0.6 per-pane identity), so concurrent panes under different accounts each report their own account, never a global "current" pointer.
  2. Headroom comes from the on-disk usage cache the @hasna/accounts warmer maintains at <accountsHome>/cache/usage/<uuid>.json (accountsHome = ACCOUNTS_HOME or ~/.hasna/accounts). The read is a synchronous readFileSyncno fetch, no cloud call — so it is safe in a stripped session and free per render.

Headroom is derived the way @hasna/accounts derives it: scoped windows are ignored; a window past its reset reads as fully replenished (100%) rather than a stale high utilization; an unclassified non-scoped window can only lower weekly headroom.

Graceful degradation — never a crash, never a wrong number

The segments render a neutral marker (5h — / 7d —, dimmed) whenever usage is unavailable:

  • the pane's account has no uuid, or the cache file is missing;
  • the cache is stale (older than 15 min — the warmer refreshes ~every minute, so past that it is down);
  • the cache is corrupt, keyed to a different account, or an error entry (e.g. a provider 429, which the warmer writes with no usage field — observed live).

usage-weekly escalates yellow→red as headroom runs low and shows green while healthy, so the binding limit reads at a glance.

Both segments are off by default (like five-hour-limit/seven-day-limit); enable with statusline enable usage-session usage-weekly.

Tests (TDD — failing tests written first)

  • test/accounts.test.ts: sessionAccountUuid (reads/rejects uuid) and sessionUsage (session+weekly headroom, zero-remaining, rollover→100, no cache, stale refused with a fresh positive control, corrupt, uuid mismatch, no-uuid, 429 error entry).
  • test/segments.test.ts: usage-session/usage-weekly render numbers, render 0%, neutral marker with no cache, and weekly colour escalation.
  • Full suite: 206 pass / 0 fail. Typecheck clean. Build clean.

Live test (real system, no cloud)

Built dist/cli.js, rendered against the real cache for this pane's own account (account090):

5h 92% · 7d 61%     # session utilization 8 → 92% left, weekly_all 39 → 61% left — matches the cache exactly
5h — · 7d —         # after a provider 429 wrote an error entry: neutral marker, not a wrong number
5h — · 7d —         # a config dir with no account uuid

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

…ounts cache

Two opt-in segments show the percent of usage REMAINING for the account this
pane is actually running as: usage-session (the ~5-hour session window) and
usage-weekly (the weekly window, the binding limit).

They resolve the pane's account uuid from oauthAccount.accountUuid in the live
CLAUDE_CONFIG_DIR state file — the same per-process signal auth-profile/auth-email
already use, so concurrent panes under different accounts each report their own —
and read the on-disk usage cache the @hasna/accounts warmer maintains at
<accountsHome>/cache/usage/<uuid>.json. The read is synchronous and local: no
cloud API call, so the segments work inside a launched, registry-stripped session
and cost nothing per render.

Headroom is derived the way accounts derives it: scoped windows are ignored, a
window past its reset reads as fully replenished rather than as a stale high
utilization, and an unclassified non-scoped window can only lower weekly headroom.
When the account is unknown, or the cache is missing, stale (>15m), corrupt, keyed
to another account, or an error entry (e.g. a provider 429), the segments render a
neutral marker (5h -- / 7d --) instead of a wrong number. usage-weekly escalates
yellow->red as headroom runs low and shows green while healthy. Both default off.

Live-tested against the real cache on station01: 5h 92% / 7d 61% for session
utilization 8 / weekly 39, and the neutral marker on a rate-limited (429) entry.

Agent: agent-ceo
@andrei-hasna

Copy link
Copy Markdown
Contributor Author

[REVIEW] GO — #18 @ 5dc1351 — lens: cache-correctness/pane-identity/no-leak, reviewer fable-usage-cache (1 of 1)

Re-anchored on head 5dc1351 (unchanged during review). origin/main merge-base == base, so the PR is not stale; existing checks are sufficient at head.

Verified against the live @hasna/accounts cache and the installed package writer:

  1. Pane account, not global/stale (same class as the 0.0.6 bug). sessionUsage -> sessionAccountUuid reads .oauthAccount.accountUuid from the per-pane state file resolved via CLAUDE_CONFIG_DIR — the same authoritative live-login blob sessionAccountEmail/liveAccountEmail trust over stale switch markers (bug 2089be70). It is the correct side of that bug, plus a second guard: the read is dropped unless the cache entry's own accountUuid equals the resolved uuid. Smoke test: a cache whose accountUuid was doctored to a different uuid returns nulls, not the wrong account's number.

  2. Reads the LOCAL cache, never the cloud API. Path resolves to accountsHome()/cache/usage/<uuid>.json — byte-for-byte the installed writer's usageCachePath (CACHE_DIR="cache", USAGE_CACHE_DIR="usage", same SAFE_UUID regex), and accountsHome() in the package is exactly ACCOUNTS_HOME or ~/.hasna/accounts and nothing else, matching the PR's accountsHomeDir. The path is fully synchronous (existsSync+readFileSync); no fetch/http/import/await anywhere in it, so it works in a registry-stripped launched session and cannot block a render.

  3. Correct numbers, matching the source of truth. deriveUsage mirrors accounts' own deriveWindowHealth: scoped windows skipped, 100 - utilization clamped, rolled-over windows -> 100, weekly folded down by unclassified windows. On the real cache entry (session util 0, weekly_all util 100, weekly_scoped scoped) it yields session=100 / weekly=0, matching accounts' own headroom:0, bindingWindow:weekly_all. Renders "5h 100%" and "7d 0%" (weekly red — binding limit legible).

  4. Unavailable/stale -> neutral marker, never a wrong number or crash. Smoke-tested: fetchedAt 20 min old (> 15 min bound), missing state file, corrupt/mismatched cache -> all return nulls -> "5h —"/"7d —" dim. Whole path is try/catch-wrapped; non-object entries and bad window fields fail safe. Where the derivation diverges from accounts (no session/weekly window present) it yields the neutral marker rather than a defaulted 100 — safe, never falsely reassuring.

  5. No token leak. The cache carries only accountUuid/fetchedAt/usage (windows: group/id/scoped/utilization/resetsAt); the segment renders only the derived percent or a dash, never the uuid, email, or any usage sub-field. No secret-shaped tokens in the diff.

Evidence: 206/206 tests pass, tsc --noEmit clean, live smoke against the real fleet cache. Segments are defaultEnabled:false (opt-in/toggleable). No concrete P0/P1 defects.

Ship the usage-session and usage-weekly segments: bump patch and cut the
CHANGELOG Unreleased section to 0.0.7.

Agent: agent-ceo
@andrei-hasna
andrei-hasna merged commit 735a802 into main Aug 7, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant