anthropics / anthropics/claude-code

Status line: expose per-model weekly rate limit (Fable) in rate_limits, or keep cachedUsageUtilization fresh

Open
#93,107 0 comments 0 reactions 0 assignees View on GitHub
area:statusline enhancement platform:windows
Dominant language
Python
Stars
145k
Forks
23.1k
PR merge metrics
PR metrics pending

Description

## 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

1. **Status line payload.** `rate_limits` carries only `five_hour`, `seven_day` and `spend_limit` (each with `used_percentage` and `resets_at`). There is no per-model window. Confirmed by capturing a live payload and against https://code.claude.com/docs/en/statusline.

2. **`~/.claude.json`.** The per-model row exists only in `cachedUsageUtilization.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`.

3. **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 the `get_usage` control request: ≈1.4 s CPU, 340 MB peak RSS.
- Python Agent SDK: ≈2.5 s CPU, 430 MB peak RSS.
- Calling `/api/oauth/usage` directly: ≈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`):

```json
"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 server `limits[]` array filtered by the overage-included-models allowlist. No new fetch.
- Document it next to `spend_limit` in 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

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by locating the status-line payload construction and the documentation section next to `spend_limit`; compare them with the `get_usage` control-request response described here. Confirm with maintainers whether Proposal A or B is in scope, then trace the chosen data path. Done means the per-model weekly window is exposed or its cache is refreshed on the stated cadence, existing consumers remain compatible, and the behavior is documented.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, typescript
Domain
api, cli, developer-experience, documentation
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.