Skip to content

fix: Codex streams without a Content-Type, MCP schema and queue gaps, TUI settings and status gaps, thread repair on the global upstream, and a login hint - #469

Merged
MagicalTux merged 4 commits into
masterfrom
fix/issue-tracker-batch
Sep 25, 2026
Merged

MagicalTux merged 4 commits into
masterfrom
fix/issue-tracker-batch

Conversation

@MagicalTux

Copy link
Copy Markdown
Member

Nine open issues from the tracker, each fixed where it was reported. Tests for every change; full suite, lint, typecheck and the strict ratchet pass (5 fewer strict diagnostics than master).

Closes #379, closes #395, closes #443, closes #444, closes #445, closes #447, closes #448, closes #449, closes #450, closes #456, closes #468.

🤖 Generated with Claude Code

MagicalTux and others added 4 commits September 25, 2026 23:42
…write queue with a depth (#447, #448, #449, #450)

- set_route.match, set_route.accounts and set_blocked_models.patterns declare
  `items: { type: 'string' }`, and set_threshold.buckets publishes its key set
  through `propertyNames`, so a model learns the constraints the validator
  already enforces from the schema instead of from a refusal (#447, #449).
- The write audit line is logged once the tool has run, as POST /teamclaude/switch
  does, and a call the tool refused is logged as refused, so the log reads as a
  record of what happened rather than of what was attempted (#448).
- The write queue takes at most eight pending turns; past that a call is
  answered at once with a tool error instead of stacking behind a minute-long
  timeout per turn (#450).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… it (#456)

The ChatGPT backend answers a Codex Responses request with no Content-Type
at all. The relay keyed streaming on that header alone, so the reply took the
buffered branch: codex-cli saw nothing until the turn was over, and the usage
booking, which lives on the streaming branch, never ran, leaving the account's
token counters at zero. A headerless success to a request whose body asked
for `stream: true` is now relayed as text/event-stream, and told so, since the
client keys on the same header.

`TopLevelFieldFinder` learns to read a bare scalar (`true`, a number) for its
field, so `parseRequestStream` finds the top-level `stream` without ever
mistaking a nested or quoted one for it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d a barred account says so (#443, #444, #445, #468)

- A settings toggle whose save fails puts the old value back in memory and on
  screen, and the log line names the setting it left alone. The gates (event
  logging, session titles, the sx mode, the client mode, the auto-redeem switch,
  the switch threshold) are read live off the shared config, so a value disk
  refused would have changed what the running server did while the next start
  quietly put the old one back (#443).
- The settings body scrolls on a short terminal, following the cursor row, with
  `↑ N more` / `↓ N more` markers where it is cut; the footer stays (#445).
- The status column reads `denied 4m` for an account under the entitlement
  cooldown and `capped` for one over its usage or spend cap, live and in attach
  mode, instead of `active` for an account that receives nothing (#468).
- The four tests that rendered rows on a bare `Object.create(TUI.prototype)`
  build a real TUI, so a field the renderer starts reading cannot break a
  routing or weekly-gating test (#444).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…laude Code's own login when it lapses (#379, #395)

- `refusesThreadContinue` keys on the effective upstream (`upstreamFor`), so a
  fleet sent to a third-party host through the global `upstream` gets the same
  message-thread repair as an account with its own. The default global upstream
  is Anthropic's host, which stays exempt, so ordinary fleets are untouched. A
  top-level `messageThreads: true` is the fleet-wide counterpart of the
  per-account flag for a global relay that does keep thread state; it applies
  on reload, and the operator line says which flag arms it off (#379).
- Claude Code checks its own login before it sends anything, in either client
  mode, and a lapsed one exits at once with "OAuth session expired and could not
  be refreshed" while the pool is fine. `run` now prints a hint naming that
  login when claude dies within seconds of launch and the local credentials are
  missing or past their expiry, and the docs say the two logins are separate (#395).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@MagicalTux
MagicalTux force-pushed the fix/issue-tracker-batch branch from 8f2f05e to dbbe4c7 Compare September 25, 2026 14:43
@MagicalTux
MagicalTux merged commit 10cb62e into master Sep 25, 2026
5 checks passed
MagicalTux added a commit that referenced this pull request Sep 29, 2026
Thirty-two commits since 1.1.21. Two change routing on an existing
config without an opt-in (#480, #481); the rest is opt-in, additive, or
display.

Behaviour changes
  #481 an API-key account's 401 is a cooldown, not a permanent `error`:
       1 min, then 5, 15 and 60 for every further rejection with no success
       in between; any 2xx/3xx resets it. The request still fails over and
       the client never sees the 401. OAuth accounts are unchanged
  #480 with session distribution on, requests carrying no session id stay
       within the top priority tier, so a fallback gateway no longer answers
       Claude Code's bootstrap and connector calls
  #470 a 200 whose SSE stream reports a provider failure before any output
       (`server_is_overloaded`, `response.failed`) fails over once, like a
       status-shaped failure would
  #465 a reload removes running accounts whose config entry is gone from
       disk, so `teamclaude remove` from another shell takes effect at once
  #460 `import` refuses an account whose token upstream has definitively
       rejected (401/403), even with `--name`; a 5xx or timeout still imports

Rename
  #483 the project is being renamed to TeamRouter (#72). This release accepts
       the new name everywhere the old one is read and changes nothing an
       install has on disk: `teamrouter` runs the same CLI, every
       `TEAMCLAUDE_*` variable is also read as `TEAMROUTER_*` (which wins when
       both are set), every `/teamclaude/…` control route also answers at
       `/teamrouter/…`, and `~/.config/teamrouter.json` is used when it exists

Features
  #441 per-account egress proxy (`accounts[].routing`: http, socks4/4a,
       socks5/5h) for refresh, probes and requests; `login --routing`,
       `teamclaude routing set/show/clear`, a connection check before it is
       relied on, and a short hold when the proxy is unreachable
  #427 `accounts[].allowExtraUsage: true` lets a paid extra-usage account
       serve once every account is past its threshold, instead of a 429
  #466 `accounts[].maxSpend`, a money cap judged against the month-to-date
       extra-usage spend upstream reports; the TUI shows what an account
       has billed
  #436 `autoRedeemResets` spends a free Codex rate-limit reset credit when
       the Codex pool runs dry (off by default)
  #482 `advisorEligibility: "strict" | "prefer"`; when the advisor model
       narrows selection to a subset of the fleet the log says so, and status
       carries the reading (`advisorNarrowing`)
  #478 `stripOverageHeaders` drops another org's per-organization billing
       headers from responses, for a pool spanning several orgs (#476)
  #471 `quota.unified5hSeenAt` / `unified7dSeenAt` in status: when upstream
       last stated each shared window
  #446 client and dimension usage for the last 5h and 24h in status and the
       dashboard, resumed across restarts
  #458 #459 #461 the dashboard sets the switch threshold, enables/disables and
       reprioritizes an account, and has a light theme remembered per browser
  #464 `l` in the TUI signs an account in `error` in again from the dashboard
  #457 status records which Codex limit meters each model
       (`quota.codexModelLimits`)
  #442 `quotaBarPercent` drops the percentage beside a TUI bar's countdown
  #451 `stripRequestFields` takes `content.<block type>` to drop content
       blocks a strict Anthropic-compatible upstream rejects
  #469 `proxy.mcp` schemas declare their item types, the write audit line
       records what happened, and the write queue has a depth (#447–#450)

Fixes
  #477 a refused WebSocket handshake whose headers all drop is relayed as a
       well-formed head instead of a blank line and body bytes
  #474 two members of one ChatGPT workspace are told apart by user id, so a
       second `login --codex` no longer replaces the first
  #469 a Codex Responses stream with no Content-Type is relayed as a stream
       and booked; thread repair on the global upstream; TUI settings and
       status gaps; a hint when a local login would have served
  #463 a Codex row with no session window draws one wide weekly bar

Tests
  #484 #485 #486 the suite asserts behaviour, not the scheduler: wall-clock
       upper bounds are gone, and subprocess tests spawn the server through
       `test-helpers/spawn-server.js`, which verifies the server it reached by
       `server.pid` (new in status) instead of trusting a port

Tooling
  #452 #453 #454 #455 docker workflow actions bumped
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment