Decision 0015 rejected the account usage endpoint because AGENTS.md forbade reading a harness's credential. The rule was written to stop one program helping itself to another's secrets, and it was catching a legitimate use with it: the user asking about their own subscription, through software they installed to do that. AGENTS.md now states the narrow allowance instead of an absolute the project does not hold, and 0016 records it. The status line stays. It is free and it speaks every turn. What it cannot do is report the per-model weekly limits a Max plan meters separately, or answer at all before a session has taken a turn. The first live reading found the account-wide seven-day window at 38% left and a per-model weekly window at 77% left — a second ceiling the footer previously could not see. Constraints the credential is read under, all enforced in code: access token only, never the refresh token; zeroed on drop, along with the file buffer it was borrowed out of; unprintable by construction, since HarnessError carries no owned strings and AccessToken's Debug is hand-written; identified as lumbridge/<version>, because sending claude-code/2.1.0 would make our traffic indistinguishable from the harness's in Anthropic's logs; and off entirely under LUMBRIDGE_CLAUDE_OAUTH=0. The request runs on a detached thread with a slow refresh and a 429 backoff, so a ten-second round trip cannot stall the transcript follower or make quitting wait on the network, and one surface failing does not fault the other two. Footer polish on top: the harness name prints once per group instead of in front of each of its four windows, each quota carries a short scope pill (5h, 7d, Fable wk, tokens) where an invisible BORDER-weight label used to be, quotas sort ahead of spend, and a window under ten percent turns its headline amber — value colour on the number, provenance colour on the meter, never mixed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
5.1 KiB
0015: Claude Code's subscription windows come from the status line, not the credential
Status: accepted; bridge installed and verified end to end. The rejection of the usage endpoint below was reversed by decision 0016 — both surfaces now run. Everything else here still holds.
Decision 0014 concluded that Claude Code cannot report a remaining balance. That was wrong, and the error was one of not looking rather than of reasoning: the transcript genuinely carries no quota, and that fact was generalised into a claim about the harness. Claude Code reports its quota through three other channels.
The four surfaces
| Surface | Carries | Cost | Needs a credential |
|---|---|---|---|
Status line rate_limits |
five_hour, seven_day — used_percentage + resets_at |
none; relayed from headers the CLI already received | no |
| Account usage endpoint | the same windows, plus per-model scoped limits | tight budget; 429s under polling | yes |
Stream rate_limit_info |
status, rateLimitType, unifiedWindows |
none | no, but requires driving the harness |
/usage in a PTY |
rendered text | a turn | no, but screen-scraped |
The status-line shape is documented inside the CLI itself:
"five_hour": { // present only while the API reports it and its resets_at has not passed
"used_percentage": number, // Percentage of limit used (0-100)
"resets_at": number // Unix epoch seconds when this window resets
}
rate_limits_available is the honest-gap signal, also documented: "False when
plan rate limits do not apply (API key, Bedrock, Vertex, or missing profile
scope)". An account with no subscription window says so, rather than leaving
Lumbridge to infer it from an absence.
Why the status line and not the endpoint
The account usage endpoint is more authoritative — it answers without an active session and exposes per-model scoped limits. It also requires reading Claude Code's OAuth credential, and AGENTS.md says Lumbridge "must not scrape their private credentials." Other tools in this space do read it; that is their call to make, and it is a real capability difference.
Lumbridge keeps the rule. The status line is pushed to a command the user
installs, so no credential is ever touched, and the data is free because the
CLI is relaying rate-limit headers it already received on its own API calls.
The cost is that a window only appears once a session has run, and that
installation edits settings.json.
The endpoint was recorded here as a rejected-for-now alternative rather than an unconsidered one, on the condition that reopening it meant amending AGENTS.md first. That is what decision 0016 does: the rule was too broad, and the two surfaces answer different questions rather than the same one twice.
Provenance
ProviderReported. Under decision 0013's test the CLI forwards the provider's
number rather than computing one — the same standing as Codex's forwarded
x-codex-* headers. The footer shows provider · exact.
The bridge
scripts/claude-statusline-bridge.sh is installed as Claude Code's
statusLine command. It appends the rate-limit fields — and only those — to
$XDG_DATA_HOME/lumbridge/claude-rate-limits.jsonl, and prints a status line.
Three properties it must hold:
- It cannot fail. A status-line command that errors or hangs degrades the user's session, so every step is guarded and it always exits 0 having printed something.
- It copies nothing else. The payload also carries the session's cost, transcript path, working directory, and model. The bridge builds a fresh record from three numeric fields per window rather than filtering the original, so there is no path by which the rest travels.
- It stays bounded. The feed rotates past 256 KB. Lines are appended rather than overwritten so concurrent sessions do not clobber each other; the probe reads the newest line, because each line is a complete snapshot and an older one is superseded, never summed.
Installation is a separate, explicit, reversible script — not something a probe
does on the user's behalf. It backs up settings.json, writes through a
temporary file, refuses to replace a statusLine someone else configured, and
supports --dry-run and --uninstall.
Window handling
Each window becomes its own account-free profile — claude-code-five-hour and
claude-code-seven-day — because two windows are two facts about two quota
periods, and merging them means silently picking one. The pane maps to the
five-hour window: it is the limit that actually stops work.
A reading's window start is derived from resets_at minus the documented
window length, which is what lets the ledger scope a burn rate to one quota
period. Two rules follow decision 0012 unchanged: a window whose resets_at
has passed is stale, so its reset is dropped and only the percentage survives;
and a start the local clock places in the future costs the start, not the
reset, which is the half that bounds a forecast.
A percentage above 100 — possible on a gateway spend limit once breached — is clamped to the whole rather than stored as more than a full window.