Skip to content

fix(perps): clear clients when reconnect readiness fails - #9868

Merged
abretonc7s merged 2 commits into
fix/perps-client-not-initialized-reconnectionfrom
fix/perps-clear-clients-on-failed-reconnect
Aug 13, 2026
Merged

fix(perps): clear clients when reconnect readiness fails#9868
abretonc7s merged 2 commits into
fix/perps-client-not-initialized-reconnectionfrom
fix/perps-clear-clients-on-failed-reconnect

Conversation

@abretonc7s

@abretonc7s abretonc7s commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Explanation

Addresses the reconnect-readiness finding raised in review on #9032; stacked on that branch.

#handleConnectionDrop constructs all four SDK clients before await newWsTransport.ready(). The catch path left them in place, so a reconnect whose readiness rejected kept isInitialized() returning true. Callers gated on isInitialized() / ensureInitialized() then issued WebSocket-backed reads (getInfoClient, getSubscriptionClient) over a socket that never opened, instead of taking the uninitialized path. Writes were unaffected — ExchangeClient runs 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:

Scenario 0837703 (before #9032) branch tip 322eda6 with this change
plain drop after initialize() isInitialized() true true false
after disconnect() isInitialized() false true false

So 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.ts gains two cases under the reconnection describe: readiness rejects after a plain initialize(), and after a disconnect(). Both assert isInitialized() === false (the first also asserts getSubscriptionClient() is undefined). 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 failed
  • eslint on both changed files — clean

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

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. If ready() rejects, the catch path used to leave those clients in place, so isInitialized() 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, #handleConnectionDrop now clears all four SDK clients and both transports (with safe close() on the WS transport), matching the cleanup initialize() already does on failure, before scheduling the existing retry.

Tests cover readiness rejection after a normal initialize() and after disconnect(); both assert isInitialized() === 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.

#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
abretonc7s requested review from a team as code owners August 13, 2026 12:49
@abretonc7s
abretonc7s merged commit 6658fa4 into fix/perps-client-not-initialized-reconnection Aug 13, 2026
5 checks passed
@abretonc7s
abretonc7s deleted the fix/perps-clear-clients-on-failed-reconnect branch August 13, 2026 12:53

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 4df3413. Configure here.

}
}
this.#wsTransport = undefined;
this.#httpTransport = undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4df3413. Configure here.

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