Skip to content

fix(dashboard): poll status before asking for a key - #381

Merged
MagicalTux merged 2 commits into
KarpelesLab:masterfrom
rikbrown:fix/dashboard-loopback-key-prompt
Sep 15, 2026
Merged

MagicalTux merged 2 commits into
KarpelesLab:masterfrom
rikbrown:fix/dashboard-loopback-key-prompt

Conversation

@rikbrown

Copy link
Copy Markdown
Contributor

Opening teamclaude dashboard on the proxy's own machine shows "Enter your proxy key to view status." The page decides this on load from localStorage alone (if (localStorage.getItem(KEY)) start(); else showKeybox();), before it has asked the server anything. But a loopback browser is key-exempt, so the operator is prompted for a key the server would not check. docs/usage.md already says "Loopback browsers are key-exempt as everywhere else", and the page does not act that way.

This change:

  • polls /teamclaude/status on load and shows the prompt only when the server refuses the request. A remote browser still gets the prompt after its first poll, as before;
  • treats a 403 like a 401. On this endpoint a 403 only comes from the key-less loopback gate: a cross-origin request, or a Host that does not name this machine (e.g. a local reverse proxy that adds no forwarding headers). A valid key clears both. Without this, that deployment would show no prompt at all, and a stale stored key there would never be cleared. tui-remote.js already reads 401/403 as "needs a key";
  • shows the error banner when a first poll fails for another reason. The banner is inside #app, which stays hidden until a status lands, so an unreachable server or a 500 used to leave a blank page.

Tests run the page's full inline script against a stub DOM, localStorage and fetch. They check that the page polls before any prompt, that a 401 or 403 brings the prompt up and drops the stored key, and that a failed first poll shows its error. The 401 case already passed before this change; the other three fail without it. Docs: the key paragraph under "Status dashboard (browser)" in docs/usage.md.

🤖 Generated with Claude Code

rikbrown and others added 2 commits September 13, 2026 10:57
The page showed the key prompt whenever localStorage held no key, before
it ever asked the server. A loopback browser is key-exempt, so the
operator on the proxy's own machine was prompted for a key the server
would not have checked.

Poll /teamclaude/status on load and show the prompt only when the server
refuses the request: a 401, or a 403 from the key-less loopback gate (a
Host that does not name this machine). A first poll that fails for any
other reason now shows its error instead of a blank page, since the
error banner sits inside the still-hidden app.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MagicalTux
MagicalTux merged commit 011c049 into KarpelesLab:master Sep 15, 2026
5 checks passed
rikbrown added a commit to rikbrown/teamclaude that referenced this pull request Sep 15, 2026
Rebases the fork onto upstream master 1779db6 (1.1.20 plus KarpelesLab#374-KarpelesLab#403).
No new fork feature of its own: this ships upstream's 13 commits and the
reshaping the rebase required.

Upstream brings a version label in the dashboard header, provider names
on the account rows of a mixed pool, Codex quota fixes across the 5h and
weekly windows, spreading for requests that carry no session id, and
outcome accounting on the same classification path every routing
decision already read.

Two fork commits dissolved into upstream, both merged there today: the
dashboard key prompt (KarpelesLab#381), and the expiring Codex header fixture,
which upstream fixed independently as KarpelesLab#402. The locally-served account
ordering went up as KarpelesLab#376, so only its fork-only documentation remains
here; and the header commit keeps the branding alone, now that upstream
resolves and draws the version itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rikbrown added a commit to rikbrown/teamclaude that referenced this pull request Sep 19, 2026
Rebases the fork onto upstream master 1779db6 (1.1.20 plus KarpelesLab#374-KarpelesLab#403).
No new fork feature of its own: this ships upstream's 13 commits and the
reshaping the rebase required.

Upstream brings a version label in the dashboard header, provider names
on the account rows of a mixed pool, Codex quota fixes across the 5h and
weekly windows, spreading for requests that carry no session id, and
outcome accounting on the same classification path every routing
decision already read.

Two fork commits dissolved into upstream, both merged there today: the
dashboard key prompt (KarpelesLab#381), and the expiring Codex header fixture,
which upstream fixed independently as KarpelesLab#402. The locally-served account
ordering went up as KarpelesLab#376, so only its fork-only documentation remains
here; and the header commit keeps the branding alone, now that upstream
resolves and draws the version itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@MagicalTux MagicalTux mentioned this pull request Sep 20, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants