fix(desktop): re-mint terminal JWT on every reconnect
Problem
Remote-workspace terminals fail to attach while everything else (files, PRs, git, ports) works. Relay logs show /hosts/<org>:<machine>/terminal/<id>?token=… returning HTTP 401 in ~0–1 ms at the relay edge, at a ~71% rate across multiple users' hosts (128× 401 vs 10× 200 on a live sample). The same host gets both 200 and 401 → a per-token rejection, not a per-host/version issue.
Root cause
The terminal WebSocket URL has a 1-hour better-auth JWT baked into it at React render time (useWorkspaceWsUrl → getWsToken()), and the desktop transport stores that full URL in transport.currentUrl. Every reconnect path — reconnectNow, scheduleReconnect, handleResume — redials transport.currentUrl verbatim, never re-minting the token. Once the JWT passes its exp, the relay (validating via better-auth JWKS) rejects the attach with 401. tRPC/filesystem calls are unaffected because they mint a fresh token per request.
The module-level token refresh (setJwt every 50 min) updates a plain variable that doesn't trigger a re-render, so the baked URL never recomputes.
Trigger: the sleep/wake + focus/visibility auto-reconnect machinery added in #5135 (and hardened in #5137) redials aggressively using the token-baked URL, converting a previously-latent staleness bug into a fleet-wide 401 storm. Surfacing via the recent canary / 1.12.6 builds. The relay validator and JWT signing config are unchanged — this is not a server/secret problem.
Fix
Stop persisting the token in currentUrl; mint it fresh per dial:
terminal-ws-transport.ts: store a token-less base URL (stripUrlToken) and append a freshly-mintedtokenat each dial — both the WS open and the fly-affinity pre-flight — via a new optionalgetTokenprovider onconnect().terminal-runtime-registry.ts: threadgetTokenthroughconnect()/reconnect(); compare against the stripped URL so a token refresh alone doesn't tear down a live shell.TerminalPane.tsx: passgetWsToken(fromuseWorkspaceClient()) as the provider.
This mirrors what web's TerminalConnection.buildUrl() and desktop's tRPC headers already do (fresh token per request).
Verification
bun test terminal-ws-transport.test.ts→ 7 passbiome check→ clean- Typecheck: no errors from the changed files (a pre-existing duplicate-
@codemirrordedup error inCodeEditor.tsxis unrelated and reproduces onmainwithout this change).
Immediate mitigation (no release needed)
Affected users can fully quit & reopen the desktop app (or reopen the terminal) to force a fresh token.
Summary by cubic
Re-mint the terminal JWT on every reconnect in the desktop app to stop 401s after token expiry. Store a token‑less base URL and add a fresh token for each dial (including pre-flight).
- Bug Fixes
- Store a token-less
currentUrl; append a fresh token per (re)dial interminal-ws-transport(WS open and relay-affinity pre-flight). - Add optional
getTokentoconnect(); thread throughterminal-runtime-registryso reconnects use new tokens. - Compare against stripped URLs to avoid tearing down a live shell when only the token changes.
- Pass
getWsTokenfromuseWorkspaceClient()inTerminalPane(@superset/workspace-client).
- Store a token-less
Summary by CodeRabbit
-
New Features
- Terminal connections now refresh authentication tokens automatically during reconnects.
- Reconnects are smarter and can reuse existing sessions when only the token changes.
-
Bug Fixes
- Reduced unnecessary terminal disconnects caused by token updates.
- Improved WebSocket reconnection reliability for terminal sessions.