Skip to content

VAPI-3542 fix(signaling): disable auto-reconnect before closing on disconnect - #13

Merged
smoghe-bw merged 1 commit into
mainfrom
fix/disconnect-disables-auto-reconnect
Aug 4, 2026
Merged

VAPI-3542 fix(signaling): disable auto-reconnect before closing on disconnect#13
smoghe-bw merged 1 commit into
mainfrom
fix/disconnect-disables-auto-reconnect

Conversation

@smoghe-bw

Copy link
Copy Markdown
Contributor

The rpc-websockets client is constructed with reconnect: true and max_reconnects: 0 (unlimited), and the library only skips reconnecting when the close code is exactly 1000. Any other code reconnects forever — including 1001/StatusGoingAway, which pv-gateway sends on every close where it wants the device to come back (heartbeat timeout, and Handle's teardown backstop).

_disconnect() removed all listeners and closed the socket but never disabled reconnect, so an explicit disconnect could leave a reconnect loop running behind a client that no longer has an "open" handler. The reconnected socket therefore never calls setMediaPreferences, never creates peer connections and never answers the heartbeat — an inert connection on the gateway with no liveness check on either side, which accumulates for as long as the process lives.

Reconnect stays enabled for the lifetime of a live connection; it is only disabled in _disconnect(), where the caller has asked to tear this client down for good. Failing to disable it no longer prevents the close.

The rpc-websockets client is constructed with `reconnect: true` and
`max_reconnects: 0` (unlimited), and the library only skips reconnecting
when the close code is exactly 1000. Any other code reconnects forever —
including 1001/StatusGoingAway, which pv-gateway sends on every close
where it wants the device to come back (heartbeat timeout, and Handle's
teardown backstop).

_disconnect() removed all listeners and closed the socket but never
disabled reconnect, so an explicit disconnect could leave a reconnect
loop running behind a client that no longer has an "open" handler. The
reconnected socket therefore never calls setMediaPreferences, never
creates peer connections and never answers the heartbeat — an inert
connection on the gateway with no liveness check on either side, which
accumulates for as long as the process lives.

Reconnect stays enabled for the lifetime of a live connection; it is only
disabled in _disconnect(), where the caller has asked to tear this client
down for good. Failing to disable it no longer prevents the close.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@smoghe-bw
smoghe-bw requested review from a team as code owners August 4, 2026 14:59
@bwappsec

bwappsec commented Aug 4, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@smoghe-bw smoghe-bw changed the title fix(signaling): disable auto-reconnect before closing on disconnect VAPI-3542 fix(signaling): disable auto-reconnect before closing on disconnect Aug 4, 2026
@smoghe-bw
smoghe-bw merged commit 6e60f01 into main Aug 4, 2026
5 checks passed
@smoghe-bw
smoghe-bw deleted the fix/disconnect-disables-auto-reconnect branch August 4, 2026 15:05
smoghe-bw added a commit that referenced this pull request Aug 20, 2026
…urns 409 (#17)

* fix(signaling): stop auto-reconnecting when the gateway returns 409

The client is constructed with `reconnect: true, max_reconnects: 0`, and the
error handler only ever treated 403 as terminal. A 409 — the gateway
rejecting the connection because another device already holds the endpoint —
was therefore treated as retryable, so the SDK reconnected several times a
second against a gateway that was correctly telling it the connection could
not be established.

Gateway logs for one endpoint show 21 such rejections in 90 seconds across
three hosts. Worse, each retry that did get a socket before being rejected
left an inert connection behind, which held the endpoint and caused the next
round of rejections — the same self-sustaining loop described in the
_disconnect comment added by #13.

Collect the terminal handshake failures into one table, disable reconnect
and surface the error for both, and return so a fatal error no longer falls
through into the generic error log. Retry policy is left to the app.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(signaling): tear down prior client before reconnecting

connect() overwrote this.ws without closing the previous JsonRpcClient,
leaving its unlimited auto-reconnect loop running in the background.
That orphaned client's "open" handler calls setMediaPreferences() via
this.ws, which by then points at the new client, so the orphan's own
socket never sends anything and just idles until the gateway reaps it
— producing repeated "new websocket connection" / "never called
setMediaPreferences" storms against the same endpoint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: smoghe-bw <smoghe-bw>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

3 participants