fix(perps): clear clients when reconnect readiness fails - #9868
Merged
abretonc7s merged 2 commits intoAug 13, 2026
Conversation
#handleConnectionDrop builds all four SDK clients before the new transport reports ready, and the catch path left them in place. A reconnect whose ready() rejected therefore kept isInitialized() true, so callers gated on it issued WebSocket-backed reads over a socket that never opened instead of taking the uninitialized path. Clear the clients and transports on failure, mirroring the cleanup initialize() already performs, before the retry is scheduled. Before this, a failed reconnect reported isInitialized() true both after a plain drop (pre-existing) and after a disconnect (new, since all four clients are now recreated on the reconnect path).
abretonc7s
merged commit Aug 13, 2026
6658fa4
into
fix/perps-client-not-initialized-reconnection
5 checks passed
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Reviewed by Cursor Bugbot for commit 4df3413. Configure here.
| } | ||
| } | ||
| this.#wsTransport = undefined; | ||
| this.#httpTransport = undefined; |
There was a problem hiding this comment.
Reconnect cleanup races subscription heal
Medium Severity
Clearing #subscriptionClient on reconnect readiness failure makes ensureSubscriptionClient treat the service as cold and call initialize during the retry backoff. That can mark the session Connected without running #onReconnectCallback, so the scheduled #handleConnectionDrop retry sees Connected and skips — leaving previously tracked subscriptions unrestored.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 4df3413. Configure here.
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.


Explanation
Addresses the reconnect-readiness finding raised in review on #9032; stacked on that branch.
#handleConnectionDropconstructs all four SDK clients beforeawait newWsTransport.ready(). The catch path left them in place, so a reconnect whose readiness rejected keptisInitialized()returningtrue. Callers gated onisInitialized()/ensureInitialized()then issued WebSocket-backed reads (getInfoClient,getSubscriptionClient) over a socket that never opened, instead of taking the uninitialized path. Writes were unaffected —ExchangeClientruns on the HTTP transport.This clears the clients and both transports on failure, mirroring the cleanup
initialize()already performs, before the retry is scheduled. The retry then rebuilds them from scratch as it already did.Scope of the regression
Measured with a rejected
ready()on the reconnect path:0837703(before #9032)322eda6initialize()isInitialized()truedisconnect()isInitialized()falseSo the post-disconnect row is the behaviour change introduced by recreating all four clients on the reconnect path; the plain-drop row was already wrong before and is fixed here too.
Test
HyperLiquidClientService.test.tsgains two cases under the reconnection describe: readiness rejects after a plaininitialize(), and after adisconnect(). Both assertisInitialized() === false(the first also assertsgetSubscriptionClient()isundefined). Both fail on the branch without this change (Received: true) and pass with it.Validation
jest tests/src/services tests/src/providers— 34 suites, 1484 passed, 0 failedeslinton both changed files — cleanReferences
Checklist
Note
Medium Risk
Touches WebSocket reconnection lifecycle in a critical trading client path; behavior change is narrow (fail-closed on failed ready) but affects when reads proceed after a bad reconnect.
Overview
Fixes a reconnect edge case where HyperLiquid SDK clients are built before
WebSocketTransport.ready()completes. Ifready()rejects, the catch path used to leave those clients in place, soisInitialized()stayed true even though the socket never opened—callers gated on initialization could issue WebSocket-backed reads on a dead connection instead of failing closed or retrying.On reconnect failure,
#handleConnectionDropnow clears all four SDK clients and both transports (with safeclose()on the WS transport), matching the cleanupinitialize()already does on failure, before scheduling the existing retry.Tests cover readiness rejection after a normal
initialize()and afterdisconnect(); both assertisInitialized() === false. Changelog updated under Fixed.Reviewed by Cursor Bugbot for commit 4df3413. Bugbot is set up for automated code reviews on this repo. Configure here.