Skip to content

Editor stuck at "Connecting…": collab-session advertises ws://…:4001 but WS is multiplexed on the main port #53

@mmoren10

Description

@mmoren10

Summary

On a default self-hosted npm run serve, the editor loads but hangs at "Connecting…". GET /documents/:slug/collab-session advertises a collab websocket URL on appPort + 1 (e.g. ws://localhost:4001/ws), but the WebSocket server is attached to the main HTTP port (:4000). Nothing listens on :4001, so the client retries forever.

Environment

  • Self-hosted, npm run serve on :4000, no COLLAB_* env vars set
  • Version: fb25787

Root cause — embedded vs. split-port mismatch

  • server/index.ts:42 attaches the WS server to the main HTTP server at path /ws:
    const wss = new WebSocketServer({ server, path: '/ws' });
  • server/index.ts:132 starts the collab runtime embedded (multiplexed on the main port):
    await startCollabRuntimeEmbedded(PORT);
    A direct handshake to ws://localhost:4000/ws?slug=<slug>&token=<accessToken> succeeds, confirming the WS lives on :4000.
  • But server/routes.ts:579 resolveRequestScopedCollabWsBase(req) decides the client-facing WS port from the COLLAB_EMBEDDED_WS env flag (routes.ts:589). When it is unset, the code assumes a split-port deployment and returns appPort + 1 (routes.ts:613):
    wsUrl.port = String(appPort + 1);
  • So collab-session returns collabWsUrl: ws://localhost:4001/ws, which is dead.

The defaults are inverted: the server attaches WS to the main port by default (startCollabRuntimeEmbedded), but the URL resolver defaults to split-port.

Reproduction

npm run serve   # no COLLAB_* env
# create a doc, then:
curl -s 'http://localhost:4000/documents/<slug>/collab-session?token=<accessToken>' | jq .session.collabWsUrl
# -> "ws://localhost:4001/ws?slug=<slug>"
lsof -iTCP:4001 -sTCP:LISTEN   # -> nothing

Editor → "Connecting…" indefinitely.

Workaround

Set one of:

  • COLLAB_EMBEDDED_WS=true (keeps the WS on the main port — routes.ts:605-607), or
  • COLLAB_PUBLIC_BASE_URL=ws://localhost:4000/ws

Suggested fix

Derive embedded-vs-split-port from the runtime that was actually started (the collab runtime already knows its wsUrlBase and whether it's attached/embedded) rather than from the COLLAB_EMBEDDED_WS env flag — or make startCollabRuntimeEmbedded set the default so resolveRequestScopedCollabWsBase keeps the same port unless explicitly told otherwise.

Found while debugging a self-hosted instance with Claude Code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions