Repository navigation
fix: a 401 fails over, unclaimed control paths stay local, and eight smaller gaps from the issue tracker - #439
Merged
Merged
Conversation
…maller gaps from the issue tracker - #412: a 401 had no handler unless the account carried a refresh token, so it was relayed to the client and the account stayed in rotation (4,124 in a row in the report). It now fails over like a 403, and an account nothing here can repair — an API key, an OAuth account with no refresh token, or a freshly refreshed token rejected again — leaves rotation with one log line. - #420: a path under /teamclaude/ that no control route claims (a typo, or the wrong verb) is answered 404 here instead of being forwarded upstream under a fleet credential. - #423: the DNS-rebinding Host check is given the address actually bound, so a server bound through TEAMCLAUDE_HOST accepts a caller naming that address. - #421: sx.org calls carry a 15s timeout and a reply size cap, so a stalled api.sx.org cannot hang a reload for good. - #422: a reload that lands while the TUI is removing an account no longer re-adds it from the not-yet-rewritten file; both adopt loops read the recorded removals. - #413: startup output is held while a TUI is coming and replayed into the activity pane and its log file once the TUI owns the console; a process that exits first gets the held lines on stderr. - #414: console.warn joins log and error in the TUI's redirect (the constructor half of this issue landed in #432). Closes #412 Closes #413 Closes #414 Closes #420 Closes #421 Closes #422 Closes #423 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ee config-rule issues The MCP route that landed meanwhile asked its Host check with proxy.host alone, the same gap #423 closes elsewhere, so keylessMcpRefusal takes the bound host. With the CLI rules now in config-ops.js: a route bucket is validated against the weekly gating keys, since a typo stored verbatim silently disabled weekly gating for that route (#424); an array switchThreshold is read as the default with one line saying so, instead of being spread into buckets named 0, 1, ... (#425); and the TUI and the status renderer import the probe ceiling and the route colours from config-ops.js rather than re-declaring them (#426). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
# Conflicts: # src/config-ops.js
# Conflicts: # src/tui.js
…sts that pinned a relayed 401 follow CI showed four existing tests encoding the behaviour this change replaces: a 401 relayed to the client, and a non-GET on /teamclaude/dashboard forwarded upstream. They now assert the proxy's own error and the local 404. Reading them also narrowed the rule. A second 401 on an account that holds a refresh token can be stale news: the forced refresh is suppressed for a short floor after a successful one, so the retry may go out on the same token. That account now only fails over; an API key or an OAuth account with no refresh token, which nothing here can repair, is what leaves rotation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Merged
MagicalTux
added a commit
that referenced
this pull request
Sep 20, 2026
Thirty-six commits since 1.1.20. Several change what a client sees on a failure, so read the first section before upgrading a shared deployment. Behaviour changes #439 a 401 from upstream is no longer relayed to the client: the request fails over like a 403, and an API-key account or an OAuth account with no refresh token leaves rotation (it used to be picked again on every request). With nothing left the client gets the proxy's own error #439 a path under /teamclaude/ that no control route claims — a typo, or the wrong verb — answers 404 locally instead of being forwarded upstream under a fleet credential #429 the synthetic 429's retry-after is the real reset of the windows blocking the request's candidate accounts, not a flat 60s, and the message counts only those candidates; #408 names accounts that need a re-login instead of calling them "at quota" #438 session pins are per conversation (session id plus a digest of the first message), so a session's subagents spread across accounts. `sessions.items[].id` in status is the composite key, load is counted per conversation, and a persisted concurrency cap re-learns #378 a `thread: continue` bound for a per-account third-party upstream is refused with the 400 Anthropic gives, so the client resends the whole conversation; `messageThreads: true` opts a relay out #434 a 429 whose x-codex-* headers show a spent account-wide window holds the account like an Anthropic rejection; a spent model-scoped bucket only moves the request #437 a Codex response head is awaited for five minutes (Anthropic unchanged) #411 idle keep-alive connections are held 120s on both listeners #389 with session distribution on, requests carrying no session id rotate on a cursor of their own instead of all resting on the current account #405 `defaultClientMode` ("mitm" | "base-url") sets what `run` and `env` do without a flag; `--mitm` / `--no-mitm` decide per launch, and in base-URL mode `env` unsets a stale proxy export naming this proxy #439 route `--bucket` is validated; an array `switchThreshold` reads as the default with one line saying so Features #419 an MCP management endpoint at POST /teamclaude/mcp, off unless `proxy.mcp` is "read" or "full"; a named client key is read-only even in full mode, and with no proxy key it serves only this machine #428 per-account `switchThreshold`, a number or a per-bucket table #406 a Claude+Codex pool is drawn as two panes on a wide terminal, each with its own current marker; #392 names the provider in a mixed list; #418 lets the operator arrange the list (`displayOrder`); #376 draws loopback-served accounts last; #435 shows the percentage beside a bar's countdown; #394 shows the running version in the header #430 free Codex rate-limit reset credits in status, the TUI and the dashboard #385 `proxy.terminalOnly` tunnels chatgpt.com so ChatGPT Desktop stays out Fixes #404 a TUI paint can no longer block the proxy (stdout non-blocking, frames dropped while the terminal is behind); #410 a dead terminal no longer takes the proxy with it, and SIGHUP shuts down cleanly #433 token usage is booked from Codex Responses streams #386 #387 #388 the Codex five-hour window is read from the model-scoped family and the usage probe, and extra limits are named from their entries #431 a headerless 429 that follows the request is retried once #432 #439 startup and collaborator log lines reach the TUI's activity pane and log file instead of the covered terminal #415 #403 #439 reload mirrors `priority`, `disabled`, `stripRequestFields` onto the config entry, and a reload during a removal does not re-add it #439 the Host check uses the address actually bound; sx.org calls time out #381 the dashboard polls status before asking for a key #403 session outcome accounting classifies the decoded path; account names in daemon log lines are sanitised Tooling #371 #372 #373 `npm run typecheck` (tsc over the JS sources) in CI, with a strict-mode ratchet: per-file strict diagnostics may not grow past the pre-merge commit (2006 at introduction, 1735 now) #401 docker workflow actions bumped Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
MagicalTux
added a commit
that referenced
this pull request
Sep 29, 2026
…enching it for good (#473) (#481) #439 stopped the 401 loop of #412 by putting an account in `error` on its first 401. For an API key nothing ever took it back out: the revalidation probe skips `error` and nothing else re-checks a key. One 401 from a gateway whose own upstream was unreachable benched a working fallback account for 22 hours, until it was toggled by hand. An API-key account is now held out of rotation for a cooldown and retried after it. The hold lengthens while the key keeps being rejected with no success in between: 1 min, 5 min, 15 min, then 1 h for every one after that. Any non-error response from the account resets the sequence. It never escalates to a permanent `error`, so a key that really is revoked costs one failed-over request per hour and a gateway that recovers comes back by itself. While held the account is unavailable to selection, to the revalidation probe and to the soonest-reset fallback, so the loop stays fixed. 401s from requests already in flight when the hold was armed do not escalate it. A reload that brings a different key, or re-enabling the account, lifts the hold. The hold is its own field beside the entitlement and routing cooldowns rather than the 429 hold, which any non-429 response clears and the probe reopens after a minute. Status carries `credentialRejectedUntil` and the reason `credential`; the renderer and the dashboard explain it. OAuth accounts are unchanged. Closes #473 Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
lifrary
added a commit
to lifrary/teamclaude
that referenced
this pull request
Sep 29, 2026
Takes KarpelesLab/teamclaude up to ba01b4f while keeping the fork's routing safeguards (403 cooldown without parking, send-failure fail-over, outbound content-length, the single predispatch wait budget, cappedMessage, the overload slot release, the dead-refresh-token guard and refresh retry, the session home wait from d7810f1, and unranked-priority semantics). Adopted from upstream, among others: per-conversation session pins (KarpelesLab#438), 401 fail-over without parking (KarpelesLab#439, KarpelesLab#473), real quota-reset retry-after and candidate counts (KarpelesLab#429, KarpelesLab#408), a headerless 429 retry (KarpelesLab#431), fail-over on a failing 200 stream (KarpelesLab#470), per-account egress proxies (KarpelesLab#441), extra-usage fallback (KarpelesLab#427), maxSpend (KarpelesLab#466), stripOverageHeaders (KarpelesLab#478), keep-alive that outlives the client pool (KarpelesLab#411), a dead terminal not killing the proxy (KarpelesLab#410), console resolution per call (KarpelesLab#432), config reload and sync fixes (KarpelesLab#465, KarpelesLab#415), and the dashboard, TUI and Codex work since 1.1.20. Integration fixes the merge needed beyond conflict hunks: - upstream request-path code that referenced upstream-only locals (sx, route, ctx.tried) rewritten for the fork's forwardRequest - resolveSwitchThreshold was declared twice after a clean auto-merge - the fork's warm-up probe now forwards a routed account through its own proxy instead of sending its credential direct (new regression test) - a routing failure during a token refresh arms the routing hold instead of parking the account; a pinned request may use an account on routing hold - the headerless-429 branch no longer writes after headers were sent - canonical-state allowlists widened for upstream's new quota fields; saves use exportState() - the fork's home wait keys on the conversation pin like selection does Tests adapted where the fork deliberately differs (warm-up probe on by default, account-anchored TUI cursor, coordinator-built Prober and Warmer, unranked priority, per-conversation pin keys, fail-over-only 401, the wait budget instead of inline waits), each with an in-file note. Full suite 2919/2919, lint, typecheck and the strict ratchet (1411 vs 1465) pass. Co-Authored-By: Claude Opus 5.5 (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.
was relayed to the client and the account stayed in rotation (4,124 in a row
in the report). It now fails over like a 403, and an account nothing here can
repair — an API key, an OAuth account with no refresh token, or a freshly
refreshed token rejected again — leaves rotation with one log line.
wrong verb) is answered 404 here instead of being forwarded upstream under a
fleet credential.
server bound through TEAMCLAUDE_HOST accepts a caller naming that address.
api.sx.org cannot hang a reload for good.
re-adds it from the not-yet-rewritten file; both adopt loops read the
recorded removals.
activity pane and its log file once the TUI owns the console; a process that
exits first gets the held lines on stderr.
half of this issue landed in fix: resolve the console at log time so reports reach the TUI #432).
Closes #412
Closes #413
Closes #414
Closes #420
Closes #421
Closes #422
Closes #423
Closes #424
Closes #425
Closes #426
Static checks only on this machine: eslint clean,
tscclean, strict ratchet nine below base. The tests run in CI.🤖 Generated with Claude Code
Added after #419 landed:
--bucketon a route is validated against the weekly gating keys (#424), an arrayswitchThresholdreads as the default with one line saying so (#425), the TUI and the status renderer import the probe ceiling and the route colours fromconfig-ops.js(#426), and the MCP route's Host check uses the bound address like the others (#423).