Skip to content

fix: recover broken publish paths and stuck reconnects - #2030

Draft
lukasIO wants to merge 2 commits into
mainfrom
claude/connection-quality-ice-restart-9ca84d
Draft

fix: recover broken publish paths and stuck reconnects#2030
lukasIO wants to merge 2 commits into
mainfrom
claude/connection-quality-ice-restart-9ca84d

Conversation

@lukasIO

@lukasIO lukasIO commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

What & why

A set of connection-recovery improvements targeting the class of failures where publishing is broken even though the peer connection still reports connected — cases the current logic doesn't notice, so the client sits on a dead session without reconnecting.

Changes

  1. Act on local ConnectionQuality.Lost (RTCEngine) — LOST is the server's verdict that it isn't receiving our media. If it stays LOST for a sustained window (5s) while connected and actively publishing, force a full reconnect. Catches the whole class regardless of root cause. Any non-LOST update cancels the pending trigger.

  2. Recreate the PC when an ICE restart has no remote description (PCTransport) — the old TODO fell through to renegotiate = true, which stalls (the pending offer is never answered). Now it throws so the caller escalates to a full reconnect (which rebuilds the peer connections). Only reachable on the resume path.

  3. Outbound-RTP liveness in verifyTransport() (RTCEngine, Room) — verifyTransport() is now async: it checks the publisher's summed bytesSent is advancing when there are active senders, and bounds how long a transport may sit in CONNECTING (previously treated as healthy indefinitely). Room's reconcile awaits it once per tick.

  4. Don't clobber a full-reconnect request that arrives mid-resume (RTCEngine) — a successful resume no longer clears fullReconnectOnNext it didn't act on (e.g. a server RECONNECT leave that arrived during the resume); it's dispatched after the attempt settles.

  5. Reconnect on a detected state mismatch instead of tearing down (Room, RTCEngine) — when the connection-reconcile safety net trips (transport silently died while we looked connected), it now triggers a full reconnect via the existing engine (reusing saved credentials, keeping the room alive) rather than terminally disconnecting. Terminal teardown remains the fallback when there's no usable engine or the reconnect ultimately fails.

Dropped

An earlier revision gated the resume on the ICE restart formally landing (waiting for restartingIce to clear via a matching-offerId answer). This was reverted: when only the signal blips but the media path is fine, the server may never answer the resume's ICE-restart offer, so gating on it hangs the resume and it never emits resumed (regressed the signalDisconnectDuringResume e2e test). A stale-but-connected publisher is instead caught at runtime by change (1) and the liveness check in (3), without changing resume semantics.

Reviewer notes

  • Behavior change: the reconcile path (DisconnectReason.STATE_MISMATCH) previously emitted RoomEvent.Disconnected immediately. It now goes Reconnecting → (Reconnected | Disconnected), matching how every other transport failure is handled. Apps that watched for an immediate STATE_MISMATCH disconnect to trigger their own reconnect will see the reconnecting flow instead.
  • verifyTransport() changed from sync boolean to async Promise<boolean> — it's @internal, and Room is the only caller.

Testing

pnpm type:check, pnpm lint (0 errors), pnpm throws:check, and pnpm test (663 tests) all pass.

🤖 Generated with Claude Code

Adds several connection-recovery improvements aimed at the class of failures
where publishing is broken even though the peer connection still reports
connected:

- Act on local `ConnectionQuality.Lost`: when the server reports it isn't
  receiving our media for a sustained period while connected and publishing,
  force a full reconnect.
- Verify the ICE restart actually landed during a resume (wait for
  `restartingIce` to clear via a matching-offerId answer) instead of only
  waiting for `connected`, and escalate a rejected publisher answer during a
  resume to a full reconnect.
- Recreate the peer connection (via escalation) when an ICE restart is needed
  but there is no remote description to restart on, rather than stalling on
  `renegotiate`.
- Add outbound-RTP liveness to `verifyTransport()` (bytesSent must advance with
  active senders) and bound how long a transport may stay CONNECTING.
- Preserve a full-reconnect request that arrives mid-resume so a successful
  resume no longer clears it.
- On a detected connection state mismatch, attempt a full reconnect (keeping the
  room alive) instead of tearing the session down.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: dcad734

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
livekit-client Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size
dist/livekit-client.esm.mjs 105.3 KB (+0.52% 🔺)
dist/livekit-client.umd.js 114.35 KB (+0.43% 🔺)

Reverts the resumeConnection change that blocked the resume until the
publisher's ICE restart offer was answered (`restartingIce` cleared). When only
the signal blips but the media path is fine, the server may never answer the
resume's ICE-restart offer, so gating on it hangs the resume and it never emits
`resumed` (regressed the signalDisconnectDuringResume e2e test).

A stale-but-connected publisher is instead caught at runtime by the local
`ConnectionQuality.Lost` handler and the outbound-RTP liveness check in
`verifyTransport()`, without breaking resume semantics.

Co-Authored-By: Claude Opus 4.8 <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.

1 participant