Status line: expose per-model weekly rate limit (Fable) in rate_limits, or keep cachedUsageUtilization fresh
Problem
/usage shows a per-model weekly window ("Current week (Fable)"). A status line cannot show that row today: the stdin payload does not carry it, and the only on-disk copy is written solely when the user opens /usage, so it is usually stale or absent.
What exists today
- Status line payload.
rate_limitscarries onlyfive_hour,seven_dayandspend_limit(each withused_percentageandresets_at). There is no per-model window. Confirmed by capturing a live payload and against https://code.claude.com/docs/en/statusline.
~/.claude.json. The per-model row exists only incachedUsageUtilization.utilization.limits[]:
``json``
{"kind":"weekly_scoped","group":"weekly","percent":57,"resets_at":"…","scope":{"model":{"id":null,"display_name":"Fable"}}}
Claude Code writes this record only from its own /usage fetch, throttled to once per 5 minutes, and its own reader discards it after 1 hour. On my machine the record sat untouched from 2026-08-28 to 2026-09-09 while the file itself was rewritten constantly. A status line that reads it shows stale data or nothing unless the user opens /usage.
- Claude Code already produces the needed shape for another surface. The stream-json control request
``json``
{"type":"control_request","request":{"subtype":"get_usage","skip_behaviors":true}}
returns rate_limits.model_scoped: [{display_name:"Fable", utilization:57, resets_at:"2026-09-10T05:59:59Z"}]. The SDK types describe it as "Per-model weekly windows from the server limits[] array, filtered by the overage-included-models allowlist. Additive — present only when the server emits them." The TypeScript Agent SDK exposes it as usage_EXPERIMENTAL_MAY_CHANGE_DO_NOT_RELY_ON_THIS_API_YET(). It needs no model call.
Why the workarounds do not fit
A status line runs on every refresh (every 10 s here). Measured cost per refresh:
claude -p --safe-mode …plus theget_usagecontrol request: ≈1.4 s CPU, 340 MB peak RSS.- Python Agent SDK: ≈2.5 s CPU, 430 MB peak RSS.
- Calling
/api/oauth/usagedirectly: ≈50 ms, but it forces the status line to read the OAuth token from.credentials.json/ Keychain. A status line script is the wrong place for a bearer token.
Proposal A: add model_scoped to the status line payload (preferred)
Mirror the shape Claude Code already emits for get_usage, using the payload's existing conventions (used_percentage, epoch-second resets_at):
"rate_limits": {
"five_hour": { "used_percentage": 75, "resets_at": 1788963000 },
"seven_day": { "used_percentage": 28, "resets_at": 1789020000 },
"model_scoped": [
{ "display_name": "Fable", "used_percentage": 57, "resets_at": 1789020000 }
]
}
- Additive and optional: absent when the server emits no per-model windows, so every existing script keeps working.
- Same source as
get_usage: the serverlimits[]array filtered by the overage-included-models allowlist. No new fetch. - Document it next to
spend_limitin the statusline docs.
Concrete consumer: https://github.com/AbdallaM-Ibrahim/claude-code-statusline (Go, single binary, no network, no subprocesses; reads the stdin payload and local files only). The payload is parsed in internal/limits/limits.go, which already handles five_hour / seven_day and would pick up model_scoped from the same struct.
If you would take a PR for this, please say so here before I open one so I can match whatever naming or gating you prefer.
Proposal B: keep cachedUsageUtilization fresh (fallback if A is rejected)
Refresh cachedUsageUtilization on the same 5-minute cadence Claude Code already uses for its own limit warnings, not only when /usage is opened, so a file-reading status line can trust fetchedAtMs.
A dedicated small file (for example ~/.claude/usage-cache.json) is preferable to ~/.claude.json: that file is ~100 KB and rewritten constantly, so polling it every refresh means re-reading and re-parsing the whole config to extract one array, and racing its frequent writes.
Environment
- Claude Code v2.1.266
- Windows 11
- Team subscription