Read Claude Code's own quota endpoint, not just its status line

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>
This commit is contained in:
Metal Agent
2026-08-31 22:04:34 -07:00
co-authored by Claude Opus 5
parent ef52aa7ce2
commit 219c674aea
13 changed files with 1674 additions and 73 deletions
+19 -7
View File
@@ -233,13 +233,25 @@ consumption against none. The parser models four token counters and nothing
else, so the conversations in those files are not representable in a Lumbridge
value. See decision 0014.
Claude Code's subscription windows come from a different surface: the CLI pipes
a `rate_limits` object carrying the five-hour and seven-day windows to whatever
`statusLine` command the user has configured, on every turn. A small installed
bridge writes those fields — and only those — to a local feed the probe tails,
so the windows are `ProviderReported` and no credential is ever read. Reading
Claude Code's OAuth token would be more capable and is what comparable tools
do; Lumbridge does not, because AGENTS.md forbids it. See decision 0015.
Claude Code's subscription windows come from two further surfaces, because
neither answers the whole question. The CLI pipes a `rate_limits` object
carrying the five-hour and seven-day windows to whatever `statusLine` command
the user has configured, on every turn; a small installed bridge writes those
fields — and only those — to a local feed the probe tails, for free and without
a credential (decision 0015). The account usage endpoint supplies what the
status line cannot: the per-model `weekly_scoped` limits a Max plan meters
separately, and an answer on a cold start before any session has taken a turn.
Reaching it means reading Claude Code's stored access token, which `AGENTS.md`
now permits under a narrow named allowance — one documented question about the
user's own account, never persisted, never logged, never in argv, no refresh
token, identified as Lumbridge rather than as the harness, and switchable off
(decision 0016).
The endpoint is the authority whenever it answers; the status line covers the
interval between its deliberately slow refreshes. Both are `ProviderReported`
and share the window arithmetic, so one quota read two ways cannot produce two
numbers. Per-model limits arrive as profiles the probe announces at runtime,
since their names come from the response.
The GPUI shell now runs both probes; a probe contributes its own profiles on
top of the declared ones, so the Claude windows appear once they report. Its
@@ -1,6 +1,8 @@
# 0015: Claude Code's subscription windows come from the status line, not the credential
Status: accepted; bridge installed and verified end to end.
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:
@@ -45,8 +47,10 @@ 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.
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
@@ -0,0 +1,87 @@
# 0016: Lumbridge reads Claude Code's credential to ask about the user's own quota
Status: accepted; verified against a live account. **Supersedes the rejection
recorded in decision 0015** and narrows the blanket prohibition in `AGENTS.md`.
## What changed
Decision 0015 chose the status line and recorded the account usage endpoint as
rejected, because `AGENTS.md` said Lumbridge "must not scrape their private
credentials." That was the right reading of the rule. The rule was too broad.
The prohibition exists to stop one program helping itself to another's secrets:
exfiltration, impersonation, using a credential for something its owner did not
intend. Reading the token Claude Code stored on this machine, to ask Anthropic
how much of *this user's* subscription is left, is none of those things. It is
the user asking about their own account through software they installed for
that purpose. The rule was written to prevent an abuse and was catching a
legitimate use with it, so `AGENTS.md` now states the narrow allowance instead
of an absolute that the project does not actually hold.
## Why the status line was not enough
It was a real improvement and it stays — it is free and it updates every turn.
Two things it cannot do:
- **Per-model weekly limits.** A Max plan meters some models separately, and
those arrive only in the endpoint's `limits[]` as `weekly_scoped` entries.
The first live reading against a real account showed the account-wide
seven-day window at 38% left *and a per-model weekly window at 77% left*
a second ceiling that the status line has no field to report. A footer that
cannot see it will say a user has room when the limit that stops them is a
different one.
- **Answering on a cold start.** The status line speaks only while Claude Code
is taking turns. Open Lumbridge in the morning and it has nothing to say
until you start a session, which is exactly when you want to know.
So both surfaces run. The endpoint is the authority whenever it answers,
because it is the account's own statement rather than a header relayed through
a session that may have ended hours ago; the status line covers the interval
between refreshes for free. Both are `ProviderReported`, and they share the
window arithmetic so the same quota read two ways cannot produce two numbers.
## The constraints this is allowed under
- **The access token only.** The credential file also holds a refresh token.
Refreshing would mean writing to another program's credential store, so an
expired token is reported as an expected gap and Claude Code renews it on its
next turn.
- **Never persisted, logged, or in argv.** `HarnessError` is `Copy` and carries
only static strings, numbers, and `io::ErrorKind`, so no error can capture the
token even by accident. `AccessToken` has a hand-written `Debug` that prints
`<redacted>`, and both it and the file buffer are zeroed on drop. That last
part is a real but partial guarantee: the token is borrowed out of the file
buffer rather than copied through an intermediate `String`, so the only two
copies are the ones this module owns and clears — but nothing here defends
against the operating system having paged either buffer out.
- **No impersonation.** The request identifies itself as
`lumbridge/<version> (usage-probe)`. Sending `claude-code/2.1.0`, as other
tools do, would make Lumbridge's traffic indistinguishable from the harness's
in Anthropic's own logs. The product boundary forbids that, and no rate limit
is worth being unable to tell who made a request.
- **The e-mail address is not read.** It sits in `~/.claude.json` beside what we
do read. The plan tier — `Max (20x)` — is taken from the credential file
because it names the quota being metered; the account identity is not needed
to render a percentage.
- **One switch off.** `LUMBRIDGE_CLAUDE_OAUTH=0` means the credential file is
never opened. A user who wants the status-line reading and nothing else has
that, and the footer degrades to exactly what decision 0015 shipped.
## Rate limiting
The endpoint 429s under polling. Refreshes are five minutes apart at the most
frequent, back off to thirty after a refusal, and run on a detached thread —
a ten-second request must not stall the transcript follower, and joining one
would make quitting Lumbridge wait on a network round trip.
A failed request does not fault the probe. Three surfaces report here; one
going quiet must not hide the two that are still working.
## Adapted from
The request shape — `GET /api/oauth/usage` with `anthropic-beta:
oauth-2025-04-20` — and the `weekly_scoped` reading are the documented
behaviour of Claude Code, observed in the `bb` and `orca` reference
implementations under `Research/agent-ides/`. No implementation code was
copied; the credential path, response fields, and status handling were read as
protocol documentation. The `User-Agent` deliberately differs.