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>
4.8 KiB
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[]asweekly_scopedentries. 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.
HarnessErrorisCopyand carries only static strings, numbers, andio::ErrorKind, so no error can capture the token even by accident.AccessTokenhas a hand-writtenDebugthat 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 intermediateString, 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). Sendingclaude-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.jsonbeside 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=0means 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.