Skip to content

fix(terminal): don't close pane WebSocket mid-handshake on teardown - #117

Merged
tdody merged 2 commits into
mainfrom
fix/terminal-ws-connecting-close
Jun 26, 2026
Merged

fix(terminal): don't close pane WebSocket mid-handshake on teardown#117
tdody merged 2 commits into
mainfrom
fix/terminal-ws-connecting-close

Conversation

@tdody

@tdody tdody commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Summary

Kills the dev-console warning WebSocket is closed before the connection is established logged from TerminalModal.tsx on (in dev) every modal open.

Cause

The terminal modal's cleanup called ws.close() unconditionally — including when the socket was still in CONNECTING. Closing a socket mid-handshake is exactly what makes the browser emit that warning.

React StrictMode (dev only) mounts every component twice — mount → unmount → remount — so opening a modal builds a throwaway WebSocket and tears it down before it finishes connecting, tripping the warning every time. A genuine fast dismiss (closing the modal before the socket connects) trips it in production too.

Fix

On teardown the cleanup now:

  • detaches the socket's onmessage/onerror/onclose handlers (so a late frame can't write to the just-disposed terminal or re-arm reconnect logic), and
  • if the socket is still CONNECTING, defers the close to onopen so it closes cleanly once connected; an already-open socket still closes immediately.

Tests (TDD)

  • New TerminalModal — WebSocket teardown tests: a CONNECTING socket must not be closed directly on unmount (handlers detached, close deferred to onopen); an OPEN socket closes immediately. Watched the first fail against the old code, then pass.
  • Added the real readyState constants (CONNECTING/OPEN/CLOSING/CLOSED) to the FakeWebSocket test double so the component's WebSocket.CONNECTING check resolves under test.
  • Full frontend suite: 599 passing; typecheck clean.

Notes

  • Independent of the backend FIFO-fd-leak fix (fix(pane-stream): release FIFO read fd on pane WebSocket disconnect #116).
  • This does not address the separate xterm Cannot read properties of undefined (reading 'dimensions') error — that's an xterm 5.3 internal render-after-dispose race, dev-only (gone in production builds), and can't be reproduced under test since xterm is mocked. Tracked separately.

🤖 Generated with Claude Code

The terminal modal's cleanup called ws.close() unconditionally, including
when the socket was still CONNECTING — which makes the browser log
"WebSocket is closed before the connection is established". React
StrictMode's mount→unmount→mount cycle hits this on every modal open in
dev (it builds a throwaway socket and tears it down before it connects),
and a genuine fast dismiss (close before connect) trips it in production
too.

Cleanup now detaches the socket's handlers and, if it's still CONNECTING,
defers the close to onopen so it closes cleanly once connected; an
already-open socket still closes immediately. Detaching handlers also
prevents a late frame from writing to the just-disposed terminal or
re-arming the reconnect logic.

Adds the real readyState constants to the FakeWebSocket test double so
WebSocket.CONNECTING/OPEN resolve under test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tdody
tdody merged commit 31f3737 into main Jun 26, 2026
3 checks passed
@tdody
tdody deleted the fix/terminal-ws-connecting-close branch June 26, 2026 00:14
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.

1 participant