Skip to content

VAPI-4011 fix(v1): harden unpublish against races, unknown ids, and disconnect - #21

Merged
smoghe-bw merged 3 commits into
mainfrom
fix/unpublish-races
Sep 23, 2026
Merged

smoghe-bw merged 3 commits into
mainfrom
fix/unpublish-races

Conversation

@smoghe-bw

@smoghe-bw smoghe-bw commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Part of VAPI-4011. Fixes six bugs in the v1 unpublish path.

  1. Race with an in-flight offer. unpublish removed transceivers outside publishMutex. offerPublishSdp sends the offer to the gateway before setLocalDescription, so a concurrent removal made the applied offer stale. Now transceiver cleanup runs under the mutex. publish() attach + negotiate also runs in one mutex section, which closes the race from the other side. The body of offerPublishSdp moves into negotiatePublishSdp, which does not lock.
  2. An unknown id unpublished every stream. An empty resolved list meant "all". Now only a call with zero arguments means all. Unknown ids log a warning and do nothing.
  3. Half-applied unpublish during an ICE blip. Local tracks still stop first. Then the SDK waits for the publish peer to reach connected (the gateway rejects offers before that) and negotiates. The wait runs outside publishMutex, so it does not block publish() or a gateway ICE-restart offer for up to 10 s. If that still fails, the SDK rejects with a BandwidthRtcError that says the stream was unpublished locally.
  4. unpublish() after disconnect() threw a TypeError. Now it stops tracks locally and returns.
  5. Unpublish during a reconnect could reopen the mic. republishStreams now skips streams that are no longer published and stops any tracks it reacquired for them.
  6. AudioLevelDetector leaked an AudioContext and a setInterval per publish. Added an idempotent stop(). The detector is stored on PublishedStream and stopped on unpublish.

Notes

Test plan

  • npm test: 5 suites, 106 tests pass (10 new)
  • npx tsc --noEmit clean
  • Manual: publish → unpublish → publish during a call, confirm audio on the far end

🤖 Generated with Claude Code

- Run unpublish's transceiver cleanup and renegotiation under one
  publishMutex section; do the same for publish's attach + negotiate.
- unpublish with ids that match no published stream is now a no-op
  instead of unpublishing every stream.
- Stop local tracks first, wait for the publish peer to be connected,
  and reject with a clear error if renegotiation still fails.
- unpublish after disconnect stops tracks locally instead of throwing.
- republishStreams skips streams unpublished mid-reconnect and stops
  any tracks it reacquired for them.
- AudioLevelDetector gains stop(); unpublish releases its AudioContext
  and sampling interval.

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

bwappsec commented Sep 23, 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(v1): harden unpublish against races, unknown ids, and disconnect harden unpublish against races, unknown ids, and disconnect Sep 23, 2026
Keeps the test valid once init() republishes only on a reconnect (#18).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@smoghe-bw smoghe-bw changed the title harden unpublish against races, unknown ids, and disconnect VAPI-4011 fix(v1): harden unpublish against races, unknown ids, and disconnect Sep 23, 2026
The wait can last 10 s and would otherwise block publish() and gateway ICE-restart offers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@smoghe-bw
smoghe-bw merged commit 3227406 into main Sep 23, 2026
5 checks passed
@smoghe-bw
smoghe-bw deleted the fix/unpublish-races branch September 23, 2026 20:44
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