Repository navigation
Quota: error recovery, probe-on-add, and usage-endpoint quota - #16
Closed
brendandebeasi wants to merge 17 commits into
Closed
brendandebeasi wants to merge 17 commits into
brendandebeasi wants to merge 17 commits into
Conversation
Extracts the `formatReset` helper into `src/index.js` and updates the
`teamclaude status` command to display time-to-reset (e.g., "resets in 2h30m")
next to the usage percentages for both unified and standard quotas.
Add a computed `remaining` summary to AccountManager.getStatus() and surface it in `teamclaude status`: session/weekly % left for Claude Max accounts, absolute tokens/requests left (with %) for API-key accounts, instead of used %.
- --headless flag runs the server without the interactive TUI (for systemd / backgrounding) - POST /teamclaude/reload re-syncs accounts from config on the running server; login/import/remove now ping it so changes apply with no restart; SIGHUP does the same (systemd ExecReload) - syncAccountsFromDisk now removes accounts deleted from config too (was add-only), keeping the in-memory config and AccountManager in lockstep
Quota utilization + reset times are only returned in /v1/messages response headers, so idle accounts show no quota until they're rotated to. Add a prober that periodically (config.pokeIntervalMs, default 10m; 0=off) sends a minimal 1-token request to each stale/idle account and feeds the rate-limit headers into updateQuota (without counting it as real usage). For Claude Max (OAuth) accounts the probe must mimic Claude Code, so the prober learns the request shape (beta header + system prompt + model) from successful live traffic and replays it. Each probe is bounded by a 20s timeout.
Add a computed `remaining` summary to AccountManager.getStatus() and surface it in `teamclaude status`: session/weekly % left for Claude Max accounts, absolute tokens/requests left (with %) for API-key accounts, instead of used %.
Headless mode, reload API, and atomic account add/remove
Background quota prober for idle accounts
The reload diff in syncAccountsFromDisk decided removals with an existence check, so when two config entries shared a name (e.g. one email across multiple orgs) removing one left the survivor's name satisfying the check for the removed in-memory account — the running server never dropped it and it lingered in `teamclaude status`. Reconcile by count instead: each disk entry claims at most one in-memory account (UUID match preferred, then name); anything left unclaimed is removed.
…ware Fix account removal not propagating on reload for same-named accounts
The same email/person can belong to multiple Anthropic orgs, each with its own OAuth token and quota. Identity was keyed on accountUuid (the person) + name (the email), so a second org overwrote the first on login, dedup collapsed them, and rotated-token persistence/removals matched the wrong entry. Introduce src/identity.js with an org-aware sameIdentity(): same accountUuid requires matching org (orgUuid, falling back to orgName), while a still-unknown org on either side is treated as the same account so a profiled login backfills a legacy entry instead of duplicating it. Capture organization.uuid in fetchProfile, carry orgUuid/orgName on accounts (and in status), and route every match site (upsert dedup, findConfigAccount token persistence, reload sync pairing, accounts dedup, TUI import) through sameIdentity. Display names stay the plain email until a second org collides, then both become "email (orgName)".
Support multiple orgs per email (org-aware identity)
A stale/rotated OAuth token left an account stuck in status 'error' with nothing to retry it: the prober skipped errored accounts and a successful refresh never cleared the flag. Now ensureTokenFresh flips 'error'→'active' on a successful refresh, and the prober force-refreshes errored OAuth accounts each tick (clearing the error when the refresh succeeds, leaving it for the next tick when the refresh token is genuinely dead). The underlying cross-contamination cause was addressed by the org-aware token persistence in the prior change.
Recover accounts from the error state
A freshly added/logged-in account had no quota data until the next poke
interval (up to 10 min), and status gave no hint of when quota was last seen.
Now a reload that adds accounts kicks an immediate prober pass, and status
reports each account's quota freshness ("updated 12s ago" / "no data yet")
plus its org. API-key accounts populate immediately; OAuth still waits for a
request template (existing prober constraint), but the freshness line makes
that state visible.
Probe quota on account add + show quota freshness in status
The prober could only learn OAuth quota by replaying a Claude Code-shaped /v1/messages request, which required first learning a template from live traffic — so a freshly added account showed no quota until real traffic flowed. Use Anthropic's GET /api/oauth/usage endpoint (anthropic-beta: oauth-2025-04-20) instead: it returns five_hour/seven_day utilization + reset directly, works with no template and no message spend, and populates quota the moment an account is added (probe-on-add now actually fills it in). API-key accounts keep the /v1/messages header probe. login/import also print the new account's current quota immediately so you can confirm it works.
Read OAuth quota from /api/oauth/usage (cold, no spend)
MagicalTux
added a commit
that referenced
this pull request
Jun 18, 2026
One Anthropic account (email/accountUuid) can hold multiple subscriptions across different organizations — e.g. a corporate Pro org and a personal Max org. The old identity model keyed only on accountUuid (with name as fallback), so adding the second org overwrote the first (#6) and --name couldn't help because the UUID check won (#7). Identity is now (accountUuid + organization). Re-implemented and reviewed against master rather than cherry-picking the closed fork PRs (#15/#17). - src/identity.js (new): orgKey / sameIdentity / matchAccounts / emailOf, the single source of truth for identity and lookups. sameIdentity treats an unknown org on either side as a match so a freshly-profiled login backfills a legacy entry instead of duplicating it. - fetchProfile: extract organization.uuid; accounts persist orgUuid/orgName. - All dedup/sync/find match sites switched to sameIdentity; syncAccountsFromDisk uses a greedy 1:1 claim so multiple same-person/different-org entries pair correctly. accounts command dedups by (accountUuid, org) and backfills org. - Name disambiguation: one org -> "email"; multiple -> "email (Org)", renaming the existing collider too. Names stay unique (they are the user-facing key). - Org-aware remove + api: resolve by name or email, --org <name|uuid> to disambiguate; lists candidates when ambiguous. - Per-account rotation priority: `priority` field (lower = preferred, default 0) as the primary sort key in _selectNext with the existing weekly heuristic as tiebreaker; new `teamclaude priority` command (--first/--last); session-reset switching won't demote to a worse priority. - Tests: identity/matchAccounts/emailOf and priority rotation (22 passing). - Docs: README, config.example.json, help text. Out of scope (future PRs from the #18 stack): #14 headless/reload API/prober, #16 usage-endpoint quota. #13 (429 account-failover) intentionally not taken — the throttle is IP-based, so the merged #25 back-off is the correct behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MagicalTux
added a commit
that referenced
this pull request
Jun 18, 2026
Completes the passive-compatible parts of the #18 upstreaming stack (#14), without the active background prober (which would violate the passive-only design). - `teamclaude server --headless` (alias `--no-tui`) runs the proxy without the interactive TUI, for backgrounding / running as a service. - `POST /teamclaude/reload` re-syncs accounts from config without a restart — the headless equivalent of pressing 'R' in the TUI. Local-only control endpoint (no upstream calls); returns the number of newly added accounts, 501 if unwired, 500 on handler error. - CLI commands that change accounts (login/import/enable/disable/priority) now best-effort notify a running server to reload, so changes apply live. A closed port refuses instantly, so it's a no-op when nothing is running. (Reload picks up adds + credential/priority/enable-disable changes; removals still need a restart.) - reloadAccounts is shared by the TUI 'R' key, the endpoint, and CLI notify. Explicitly NOT included (active polling, against passive-only): the background quota prober, usage-endpoint quota, and probe-on-add from #14/#16. Tests for the reload endpoint (success/501/500); README + help updated. Co-authored-by: Claude Opus 4.8 (1M context) <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.
Stack position: 3 of 4. Builds on #14 and #15 (merge those first). The diff shown here includes their commits until they merge; the changes unique to this PR are below.
What this adds (net-new vs #15)
errorstate are re-evaluated and recovered automatically instead of staying dead until restart (account-manager.js,prober.js).account-manager.js,index.js).account-manager.js,oauth.js,prober.js,index.js).Why
Makes quota tracking accurate and self-healing: accounts recover on their own, new accounts are usable immediately, and quota can be read without cost.
Test plan
errorrecovers without restart.