Skip to content

fix(web): settle RPCs parked on reconnect readiness - #1738

Merged
chuks-qua merged 1 commit into
mainfrom
fix/ws-ready-orphaned-rpc
Sep 22, 2026
Merged

chuks-qua merged 1 commit into
mainfrom
fix/ws-ready-orphaned-rpc

Conversation

@chuks-qua

@chuks-qua chuks-qua commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

What

ws-transport parked RPCs on a ready promise that could leave them unsettled forever. The original orphan (reconnect replacing a pending ready) was already fixed by the readyPending guard in #1736; this PR closes the remaining gaps against #1733's acceptance criteria.

  • close() now rejects ready with Transport closed instead of resolving it. Previously parked RPCs resumed onto a CLOSED socket where send() silently drops the frame, leaving the request in pending forever. Late RPCs issued after close now reject instead of queueing onto a dead transport.
  • rpc() and rpcBinary() reject with WebSocket disconnected when the socket is no longer OPEN after await ready (covers the open-then-close-before-continuation race and the post-close call path). The readiness policy is now identical on the JSON and binary paths.
  • The reconnect timer callback bails if close() lands while it is awaiting discoverServerUrl; previously it still ran connect(), leaking a socket and re-arming a fresh pending ready that would park future RPCs forever.
  • waitForConnection propagates the ready rejection instead of hanging until timeout.

Why

Fixes #1733. An operation awaiting readiness could hang forever even after the app reported connectivity restored; on the audited commit the parked RPC was still pending after reconnect + 60s.

Evidence

Regression coverage lives in apps/web/src/__tests__/connection.test.ts under the normal Vitest config (7 new tests). The fixture models real browser semantics: send() throws while CONNECTING and silently discards while CLOSED, which is what made the close-path orphans reproducible.

  • Red on HEAD: parked-RPC-on-close, post-close RPC, and binary-on-close tests stayed pending past 60s of fake time.
  • Green after fix: 17/17 in connection.test.ts, 38/38 in the sibling transport suites (agent-commands, thread-reconnect-refresh, terminal-reconnect-selection, running-session-hydrate, ws-events); oxlint and tsc --noEmit clean for apps/web.
  • Live drive against the worktree runtime in real Chromium: closed the app's socket (reconnect banner shown), clicked fixture-repo while reconnecting so listThreads parked on ready, killed the next socket while CONNECTING (readyState === 0 confirmed), next attempt opened, the parked RPC resumed and the project expanded to "No active threads". Zero console errors.

Recovered: fixture-repo expanded after a killed CONNECTING reconnect

Review Notes

No automatic retries were added; already-sent requests still settle only via response or rejectPending, so mutations cannot be duplicated by the transport. The verification boundary matches the issue's: deterministic fake-socket coverage plus a live reconnect drive; no real network fault injection claimed.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

An RPC awaiting the `ready` promise during a failed CONNECTING attempt, a
close(), or a socket that dies between readiness and send could sit in
`pending` forever: close() resolved `ready` so waiters resumed onto a dead
socket where send() silently drops, and a reconnect callback already awaiting
URL discovery could still create a fresh socket and pending `ready` after
dispose.

close() now rejects `ready` so parked and late RPCs fail fast, rpc() and
rpcBinary() reject when the socket is no longer OPEN after `await ready`, the
in-flight reconnect callback bails if closed during discovery, and
waitForConnection propagates the rejection.

Fixes #1733
@chuks-qua
chuks-qua merged commit e3ac202 into main Sep 22, 2026
6 checks passed
@chuks-qua
chuks-qua deleted the fix/ws-ready-orphaned-rpc branch September 22, 2026 08:54
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(transport): RPCs waiting on a failed reconnect remain pending after reconnection

1 participant