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:
co-authored by
Claude Opus 5
parent
ef52aa7ce2
commit
219c674aea
@@ -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.
|
||||
Reference in New Issue
Block a user