write down where the remote pane socket bug stands

A pane on a remote server reads fine and never connects its socket. Captured what has been
ruled out by direct test — the server accepts that exact key over wss with and without a
browser Origin, on the first try — so the next session does not re-derive any of it.

The remaining question is client-side lifecycle with several sockets mounted at once, and the
first move is instrumentation rather than theory: the console says a close arrived during
CONNECTING and does not say who called it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 22:25:41 +01:00
co-authored by Claude Opus 5
parent 243bd04d97
commit 907ac46eec
+74
View File
@@ -0,0 +1,74 @@
# Multi-pane chat: where it stands, and the one open bug
Written 2026-08-10 at the end of a long session, so the next one starts from evidence instead of
re-deriving it.
## The open bug
**A pane pointed at a REMOTE server shows `Disconnected` and never gets a reply.** The pane opens, lists
and reads that server's conversations fine (HTTP with the API key works). Sending produces nothing.
Console, repeatedly:
```
WebSocket connection to 'wss://officer.pastilhas.dev/api/chat/ws?token=ofk_…' failed:
WebSocket is closed before the connection is established.
```
The same message also appears for the LOCAL pane's `ws://localhost:9010/...`, yet the local pane works
and shows connected. So the message alone is not the bug — something closes the remote one and it never
comes back.
## Ruled out, each by direct test — do not re-test these
- **The server.** `wss://officer.pastilhas.dev/api/chat/ws?token=<the same ofk_ key>` opens on the first
try from outside the browser. Tested twice: with no `Origin` header, and with
`Origin: http://localhost:9010` (what the browser sends). Both `OPEN`.
- **The key.** Same key, works over HTTP for `/chat/sessions`, and opens the socket above.
- **Websocket auth not understanding API keys.** `upgradeWs` in `server.tsx` uses `resolveAuthToken`, the
same resolver as the HTTP doors, and comments say so explicitly. `chat` is an `execution` capability
and the owner passes `isWsProviderAllowed`.
- **Officer being stale on alpha.** Pulled and restarted; the failure persists.
- **Send being dropped.** Fixed in `243bd04``useChatWebSocket.send` used to `return` silently when the
socket was not `OPEN`. It now queues and flushes on open. That fix is real and worth keeping, but it
did not resolve this: a socket that never opens never flushes.
## Where to look next
`src/workspaces/hooks/src/useChatWebSocket.ts`, and specifically what happens with SEVERAL instances
mounted at once — one per pane.
The handlers look individually correct: `close` returns early when `socketRef.current !== socket`, the
effect depends on `[url]` alone, and `isCleaningUpRef` is reset on mount. What has NOT been established
is how those interact across three simultaneous instances plus React's dev StrictMode double-invoke,
which creates, closes and recreates every socket on mount.
Concrete things to try, cheapest first:
1. **Instrument before theorising.** Log `url`, `readyState` and instance identity on every create,
open, close and retry. The console message says a close arrived during CONNECTING; it does not say
who called it. That is the whole question and it is one log line away.
2. **Check whether the effect re-runs.** `url` is a string, so it should be stable — but
`chatSocketUrl(serverId, localStorage.getItem('BEARER_TOKEN'))` is recomputed every render, and if
anything makes `serverId` flip (say, a pane re-resolving its target) the URL changes and the socket
is torn down mid-handshake, forever.
3. **Suspect the transcript resolver in `ChatPane`.** It calls `onTargetChange` on success, which
rewrites the tab state, which produces a new `target` object for every pane in that tab. If that ever
loops, every pane's socket is rebuilt on every pass and none survives the handshake.
4. **Try one pane pointed at the remote server, alone.** If it connects, the bug is about multiplicity
rather than about the remote server, which would be the single most useful fact to have.
## What works, so it is not re-litigated
- Adding a second server by URL + `ofk_` key, verified against `/api/auth/me` before storing.
- Per-pane server chips; the list, the directory picker and the transcript all follow the pane's server.
- Tabs, splitting to three panes, closing, renaming a tab, and the tab name winning the page title.
- Reading a remote conversation end to end.
## Setup facts worth not rediscovering
- Andre's Mac key lives in agent memory. Alpha needs its own; the one in the console above is alpha's.
- `bun dev` on the Mac serves a STALE bundle whenever a new file is added to a workspace package — the
symptom is `X is not a function` for an export that plainly exists. Restart it; do not debug it.
- The Mac is `https://macbook.pastilhas.dev` through NPM on alpha (`100.64.0.1` → Mac `100.64.0.9:9010`),
and the cert is valid under strict TLS.