fix(client): reject WebSocket link calls when connect returns a closed socket - #2083
Merged
dinwwwh merged 1 commit intoSep 25, 2026
Merged
Conversation
…d socket A socket that is already closing or closed never fires `close` again for listeners added later, and `send` silently discards data, so calls on it never settled when reconnect was disabled. Treat such a socket as a failed connection: calls reject immediately, and with reconnect enabled the link retries without attaching listeners to the dead socket.
Contributor
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
WebSocketLinkTransport.getConnectedPeerreadyState guard — afterconnectresolves, a socket whosereadyStateis neitherCONNECTING(0) norOPEN(1) now throwsAbortError('WebSocket is already closing or closed')before any peer/listener is created, so calls on a pre-closed socket reject instead of hanging.- Reconnect path preserved — the throw flows through the existing attempt
.catch; with reconnect disabled it rejects (and the rejectedthis.currentis reused), and with reconnect enabled it retries without attaching listeners to the dead socket. - Test coverage —
rpc-link.test.tsgains a parameterizedclosing/closedrejection test with reconnect disabled (asserts both calls reject,send0,connectcalled once) and a closed-then-open reconnect test (asserts retry,closedSocket.addEventListener0). Both fail on pre-fix code for the right reasons.
I traced the guard against getConnectedPeer's caching/retry logic and the concurrent-call path — the readyState check sits in the only window where listeners would otherwise miss the close event (JS runs the subsequent synchronous listener registration before any close-event task can fire), so the fix is correctly scoped. Locally: packages/client suite passes (506 tests).
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/experimental-effect
@orpc/evlog
@orpc/hibernation
@orpc/json-schema
@orpc/experimental-lock
@orpc/experimental-msw
@orpc/nest
@orpc/next
@orpc/node
@orpc/openapi
@orpc/opentelemetry
@orpc/pinia-colada
@orpc/pino
@orpc/publisher
@orpc/ratelimit
@orpc/server
@orpc/shared
@orpc/swr
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/zod
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

With reconnect disabled (the default), calls through the WebSocket link hung forever when
connectreturned a socket that was already closing or closed. They now reject immediately withAbortError('WebSocket is already closing or closed').This happens when the socket is created ahead of time, as in the hibernation docs (
connect: () => websocket): if the server is down and the socket closes before the first call, that call never settled. Acloselistener added after the socket closed never fires (confirmed on Node 24 for both the globalWebSocketandws), andsendsilently discards data on a closed socket, so the close handling from #2081 never ran.Fixes
Notes
WebSocketLikemust reportreadyStateasCONNECTING(0) orOPEN(1) to be used; any other value is treated as closed.Testing
rpc-link.test.tscover an already closing and an already closed socket with reconnect disabled (both hang onmain) and an already closed socket with reconnect enabled.WebSocketclosed by a refused connection: the call stays pending onmainand rejects immediately with this change.pnpm vitest run packages/client/src/adapters/websocket tests/rpc, eslint, and@orpc/clienttype-check pass.