Repository navigation
feat(auth): read Codex and Claude usage on a timer for soonest-reset (MASTRA-787) - #7
Merged
Merged
Conversation
…(MASTRA-787) The service reads each Codex and Claude OAuth credential's usage endpoint at startup and every five minutes, maps the response onto the quota signals the soonest-reset selector already ranks on, and records it on the credential without touching counters, cooldowns or the auth file. The selector now sees quota consumed outside the proxy and credentials the proxy has never used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The usage reads run serially on one timer, and the executor HTTP client has no timeout, so the per-read deadline is what keeps one hung read from stopping every later observation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MASTRA-787 Read Codex and Claude usage on a timer so soonest-reset sees quota used outside the proxy
OutcomeThe soonest-reset selector in WhyToday the selector knows a credential's quota only from the proxy's own request traffic: response headers, and Codex websocket quota events (MASTRA-765). On 2026-10-04, reading the provider usage endpoints on studio2 showed two problems (details on MASTRA-766):
Eric chose (2026-10-04) to read usage for every credential on a timer, rather than only once for credentials with no observation. Scope
Acceptance criteria
Constraints
Source refs
Design pass (Astra, gpt-6-astra high, 2026-10-04) and adjudicationAccepted, verified against code:
Added by me:
Rejected:
|
…-787) The Codex usage parser now copies each non-null window as given instead of dropping windows that fail range and reset checks. ObserveQuotaHeaders no longer pushes a clone to the scheduler, which reads no observation fields. The usage read loop runs on Run's own context, so the quotaReadsCancel field and its Shutdown branch are gone. Both usage parsers move to helps/quota_usage.go, and codex_quota.go and claude_ratelimit.go return to their upstream content. The end-to-end sweep test now fails when the sweep does not finish, checks the exact usage reads, and checks the saved cooldown records. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linear: MASTRA-787
The soonest-reset selector learns a credential's quota only from the proxy's own traffic. On studio2 that hides the Codex account that resets soonest, litman.org-pro, which Eric uses outside the proxy. It also leaves the Claude utilization stale. This PR reads each Codex and Claude OAuth credential's usage endpoint every 5 minutes and feeds the reading into the observation the selector already ranks on.
Commits
2fb67d24feat(auth): read Codex and Claude usage on a timer.sdk/cliproxy/service_quota.go: one loop owned by the service. It sweeps once at start, then every 5 minutes, and is cancelled on shutdown.GetByIDfor the current credential and skips anything that is not OAuth with a valid access token. It then callsManager.HttpRequest, which goes straight to the executor. That path only setsAuthorizationfrom the current token: it never refreshes, never callsMarkResult, and never writes the auth file.ParseCodexUsageHeadersandParseClaudeUsageHeaders(nowinternal/runtime/executor/helps/quota_usage.go) map the responses onto the existingX-Codex-*andAnthropic-Ratelimit-Unified-*signals. Claudeutilizationarrives as a percent and is stored as a fraction, andresets_atis stored as Unix seconds. Nothing ranks on the extra limits (additional_rate_limits,iguana_necktie), so they are not mapped.Manager.ObserveQuotaHeaders(quota_signals.go) replaces only the credential-wideAuth.Quotasnapshot, underm.mu, then persists the cooldown state (.cds). Counters, availability and cooldown fields are untouched.51f8a205docs(agents): adds the 30 s per-read deadline to the AGENTS.md timeout exceptions. The executor's HTTP client has no timeout, and the reads run serially, so one hung read would otherwise stop every later observation. This is the same kind of exception as the management APICall timeout already listed.93889015refactor(auth): subtract review findings. Review was/pstack:interrogate(verdict below).used_percentabove 100 or a missinglimit_window_seconds. A read replaces the whole snapshot, so dropping a window erased the exhaustion the selector gates on. New test: 100.4% with no window length still maps.ObserveQuotaHeaders. The scheduler reads only cooldown fields fromQuota, and soonest-reset ranks fromm.auths.quotaReadsCancel. The loop runs onRun's own context.helps/quota_usage.go.codex_quota.goandclaude_ratelimit.goare back to upstream byte for byte, which keeps fork code out of upstream files for rebases..cdsrecords hold codex-c's new reset and codex-a's previous one.Size against main: 158 non-test Go lines, 1 AGENTS.md line, the rest tests.
Acceptance criteria (MASTRA-787)
quota_usage_test.gochecks the recordedwham/usageandoauth/usageresponses, with identities removed:TestQuotaUsageSweepRoutesNewSessionToUnusedCredentialruns the realRunand HTTP handler. It uses three synthetic Codex OAuth credentials and one Claude credential, withsoonest-reset, and every upstream call goes to a mock round-tripper. codex-c has never been used, and its usage read shows the soonest reset, so a new session goes to codex-c. With the sweep start removed, the test fails: "new session went to [tok-a], want [tok-c]"..cdsstore was written.NextRetryAfterandGenerationunchanged.Checks, which I ran myself on the committed tree 9388901 (the gate also passed earlier on 51f8a20):
go build ./...: exit 0.go vet ./sdk/cliproxy/... ./internal/runtime/executor/...: exit 0.go test ./...: exit 0.go test -race -count=1 ./sdk/cliproxy/ ./sdk/cliproxy/auth/ ./internal/runtime/executor/helps/, eight logged runs:TestHandleAuthUpdates_SameRevisionWaitDoesNotWaitForOtherAuthInBatchandTestEndToEndAuthFile{Replacement,Patch}_RestoresModelsInV1ModelsWithoutRestart.s.startQuotaUsageReads(ctx)commented out,TestQuotaUsageSweepRoutesNewSessionToUnusedCredentialfails.CodexExecutor.HttpRequestandClaudeExecutor.HttpRequest, with exactly the headers this PR sends. All 6 returned 200: Codex over HTTP/2, and Claude over HTTP/1.1 through the uTLS transport. All 6 mapped 4 or 5 signals, and no auth file's mtime changed.Not yet verified, and required before deploy:
gpt-reservelimit, 9 s apart, movedreset_atby 9 s. Claude does not: every Claude 5-hour window was running at test time.Review
Code review:
/pstack:interrogate. Three reviewers: Opus 5.5 at max (8 findings), gpt-5.6-sol at max (4), and Grok 4.7 at xhigh (4).Acted on (commit 3):
Disproved as a functional failure: "the reads lack the Codex and Claude Code client headers, so the hosts reject them". Raised by Grok and Opus. The live read above returned 200 for all 6 credentials.
Open for the owner: the reads still present Go's default User-Agent, from the same IP as each account's CLI traffic. An executor-owned read with the inference headers is the alternative.
Noted, not acted on:
.cdsfile, about 6 per sweep (Sol).Shutdownno longer stops the loop by itself.Run's exit does, with at most one 30 s read in flight (from the fix lane).Dismissed: "a replacement credential is corrupted by an in-flight read" (Sol). It needs an auth file swapped to a different account under the same name within 30 s, which normal operation never does.
Design: Astra (gpt-6-astra, high). The adjudication is on MASTRA-787.
Build: Opus 5.5 at medium. I read the full diff and re-ran the checks above.
Note on the lane: while it was checking that the test fails without the sweep, an early version of the service test let the auto-refresh loop send the fake refresh tokens to OpenAI's real token endpoint, which answered 401. The current test uses tokens with no refresh token that expire in 10 days. Any request not matched by the mock is recorded as a test failure.
🤖 Generated with Claude Code