The footer showed invented percentages. It now shows what two harnesses actually report, or says it does not know. lumbridge-core gains an append-only per-profile UsageLedger and a projection that labels every derived value estimated, withholds a burn rate from a single sample, withholds a window fraction with no reported ceiling, withholds an exhaustion estimate that lands after the reset, and reports an expired window as rolled over rather than freezing its last percentage. A missing fact renders as missing, never as zero. (0012) lumbridge-harness is the impure side: processes, clocks, and untrusted wire text in, observations out. Three adapters: - Codex's account/rateLimits/read over the app-server's JSON-RPC stdio. The client cannot express a request outside a two-variant enum and answers every server-to-client request with -32601, so a harness asking Lumbridge for a credential is refused by construction. (0013) - Claude Code's session transcripts, as a byte-offset tail follower that reports nothing until the backlog is read to EOF — a partially-read backlog is indistinguishable from a burst of spend, and the first run against 20 MB reported forty-six billion tokens an hour. The parser models four counters, so the conversations in those files are not representable. (0014) - Claude Code's five-hour and seven-day subscription windows, via a bridge installed as its statusLine command. 0014 had claimed no such surface existed; it does, and the record is corrected in place rather than quietly edited. Lumbridge does not read the OAuth credential to call the account usage endpoint, which is what comparable tools do — AGENTS.md forbids it, and 0015 says so rather than leaving the gap unexplained. Also in here: a capability-check ordering fix in the workspace reducer, where the applied-request replay table was consulted before the capability check and so answered questions the caller had no right to ask; the GPUI spike wired to the live probes with per-harness gauges and provenance chips; and a launcher that matches its own window by PID, because GPUI sets WM_NAME but not _NET_WM_NAME and a title match never succeeded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
98 lines
4.8 KiB
Markdown
98 lines
4.8 KiB
Markdown
# 0015: Claude Code's subscription windows come from the status line, not the credential
|
|
|
|
Status: accepted; bridge installed and verified end to end.
|
|
|
|
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:
|
|
|
|
```text
|
|
"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 is recorded here as a rejected-for-now alternative rather than an
|
|
unconsidered one. Reopening it means amending AGENTS.md first.
|
|
|
|
## 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.
|