Skip to content

fix(live-ws): resolve the public socket URL at runtime - #11377

Merged
diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.50from
ntdat812:fix/live-ws-public-url-11331
Aug 24, 2026
Merged

diegosouzapw merged 1 commit into
cheaper-inference:release/v3.8.50from
ntdat812:fix/live-ws-public-url-11331

Conversation

@ntdat812

Copy link
Copy Markdown
Contributor

Fixes #11331.

⚠️ base-red inherited: #9985 — release/v3.8.50 is not green; the failures this PR
inherits are the ones tracked there, not introduced here.

Root cause

The runtime-discovery path already exists and is there for exactly this deployment:
the browser reads /api/v1/ws?handshake=1 because NEXT_PUBLIC_* is inlined at
build time, so a prebuilt Docker/npm image can never carry an operator's value.
useLiveDashboard.ts says so in its own comment.

But the server side of that handshake resolved the URL like this:

function getLivePublicUrl(): string | null {
  const publicUrl = process.env.NEXT_PUBLIC_LIVE_WS_PUBLIC_URL;   // build-time-shaped name
  ...
}

so in a prebuilt image it had nothing to echo. The client then fell through to
getDefaultWsUrl(), which hardcodes :20132 — the reporter's
wss://omniroute.domain.tld:20132/live-ws, unreachable behind Traefik, and the UI
sits on "Live disabled — WebSocket disconnected. Showing last known state."

LIVE_WS_HOST / LIVE_WS_PORT are read at runtime (see resolveEmbedWsHost),
which is why setting them looked like it should have worked.

Change

resolveLiveWsPublicUrl(env) in src/shared/utils/wsPath.ts, next to
deriveLiveWsPath:

  • reads LIVE_WS_PUBLIC_URL at runtime — the un-prefixed name, consistent with
    LIVE_WS_HOST / LIVE_WS_PORT;
  • keeps NEXT_PUBLIC_LIVE_WS_PUBLIC_URL as the fallback, so any deployment already
    setting it is unaffected;
  • accepts only ws:// / wss://, the same guard sanitizeWsPublicUrl applies on
    the client, and trims surrounding whitespace;
  • getLiveWsPath() derives from the resolved value, so a proxy path like
    /omniroute/live reaches the client through the handshake too.

The handshake route now calls it. The hardcoded :20132 default is untouched —
it is still correct when no public URL is declared, and changing it to same-origin
would need an upgrade handler on the HTTP port that src/proxy.ts does not have
(the comment in getDefaultWsUrl records why that was tried and reverted).

Tests

tests/unit/live-ws-public-url-11331.test.ts (new, 7 cases): the runtime name
honoured; the prefixed name still working and losing to the runtime one; non-ws
schemes rejected (https://, javascript:, bare host:443); blank/whitespace
falling through to the next candidate; surrounding whitespace trimmed rather than
rejected; the path following the resolved URL; and a source assertion that the
handshake route no longer reads the build-time-only name directly.

Mutation check — restoring the route's old body fails exactly:

not ok 7 - #11331 the handshake route resolves the URL at runtime

The three existing live-ws suites (instrumentation-live-ws, live-ws-cookie-parse,
live-ws-eaddrinuse-6324) stay green. npx tsc --noEmit clean; eslint clean with
the repo's suppressions file.

Changelog fragment: changelog.d/fixes/live-ws-public-url-runtime.md.

@ntdat812
ntdat812 requested a review from diegosouzapw as a code owner August 24, 2026 10:51
The browser discovers the Live socket through `/api/v1/ws?handshake=1`
precisely because `NEXT_PUBLIC_*` is inlined at build time and a prebuilt
Docker or npm image can never carry an operator's value. The server side
of that handshake read only the `NEXT_PUBLIC_`-prefixed name, so it had
nothing to echo: behind a reverse proxy the dashboard kept dialling
`wss://<host>:20132/live-ws` and sat on "Live disabled".

`resolveLiveWsPublicUrl` reads `LIVE_WS_PUBLIC_URL` at runtime, alongside
the existing runtime `LIVE_WS_HOST` / `LIVE_WS_PORT`, and keeps the
prefixed name as the fallback so deployments already setting it are
unaffected. Only `ws://` and `wss://` are accepted, matching the guard
the client already applies, and the derived path follows the URL.

Fixes cheaper-inference#11331
@ntdat812

Copy link
Copy Markdown
Contributor Author

CI note, so the red is not left ambiguous.

Unit Tests fast-path — openapi-coverage.test.ts — is base-side, not this PR.

AssertionError: OpenAPI operation coverage regressed: 34.5% < floor 34.6%

The same assertion fails identically on #11374, #11376, #11377 and #11380, whose
diffs are disjoint (bin/cli/*, src/sse/services/auth.ts, src/shared/utils/wsPath.ts,
src/lib/tokenHealthCheckKimi.ts). None of them adds or removes an API route, so
none can move that ratio. The floor sits 0.1 point above what release/v3.8.50
currently documents — routes landed without their openapi entries.

Build (advisory) and dast-smoke are the inherited base-red already tracked in #9985.

I have not touched the coverage floor: rebaselining a gate I did not break is not
mine to do, and it would hide the drift. Say the word if you would rather it be
bumped, or if you want the undocumented operations written up.

@diegosouzapw
diegosouzapw merged commit 9f30b76 into cheaper-inference:release/v3.8.50 Aug 24, 2026
13 of 16 checks passed
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…ence#11377)

Merged via consolidated batch validation. Fixes live-ws public socket URL resolution for prebuilt Docker/npm images, where NEXT_PUBLIC_* is inlined at build time and can never carry an operator's runtime value. Own test passes.
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.

[BUG] Combo Studio frontend ignores proxy variables and forces hardcoded WebSocket port 20132 (/live-ws)

2 participants