msitarzewski / msitarzewski/openstudio

Return feed test fails consistently on Node 22, passes on 18/20

Open
#16 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
121
Forks
43
Avg merge
52m
Merged PRs (30d)
3

Description

Good first bug for someone with WebRTC curiosity: narrow, reproducible, self-contained, and no infrastructure or account access needed.

Symptom

tests/test-return-feed.mjs fails with peer A never receiving B's return feed:

--- Verifying return feed playback ---
[A] Waiting for return feed from B...
[A] Return feed info: { count: 0, feeds: [] }
❌ TEST FAILED WITH ERROR: A should have 1 return feed playing, got 0

In the product this means a participant can permanently hear silence — it is not only a test problem.

What makes it tractable

It is not flaky. On the same commit, verified across multiple runs:

Node Result
18 passes consistently
20 passes consistently
22 fails consistently

Playwright pins its own chromium, so the browser is byte-identical across those three jobs. The only variable is host timing — Node 22 is faster, which shifts when the second peer renegotiates relative to the first finishing setup. That points at a startup race rather than anything in the media path.

CI currently runs this test with continue-on-error: true so it does not gate other work. Please remove that flag as part of the fix.

Already ruled out — please do not re-investigate

  • Signalling delivery. Renegotiation offers are delivered. Instrumented live in DevTools: the receiver both dispatches the offer and enters ConnectionManager.handleOffer. An earlier theory that the offer was lost in transit is disproven.
  • Peer connection teardown. createPeerConnection() reuses an existing connection (rtc-manager.js:169), so renegotiation does not destroy peer state.
  • Transceiver collapse. This was a real bug and is already fixed. addReturnFeedTrack() used pc.addTrack(), which reuses a compatible recvonly transceiver — including the one created by the other peer's return feed — collapsing both feeds onto one m-line so whoever answered last lost their sender. Now uses addTransceiver(track, {direction: 'sendonly'}). Fixing it took Node 18 and 20 from failing to passing, but not 22.

The most promising lead (unverified)

Perfect negotiation has the impolite peer ignore a colliding offer. That is correct for an initial offer, but for a renegotiation it drops the polite peer's track permanently — there is no retry anywhere. That matches the "silent forever" symptom exactly.

An impolite peer that ignores an offer arguably should trigger its own renegotiation afterwards so the dropped tracks get re-offered. See web/js/connection-manager.js:295-310.

Worth first adding logging on the receive path: in failing runs the sender logs a sent offer and the receiver logs nothing at all — not even the "Offer collision detected" line it would print if it were deliberately ignoring. That gap is unexplained and is where I would start.

Reproducing

git clone https://github.com/msitarzewski/openstudio.git
cd openstudio && npm install && cd server && npm install && cd ..
node server/server.js &
node tests/test-return-feed.mjs        # use Node 22
⚠️ The trap that will cost you an afternoon

If you debug this by hand in a browser instead of via Playwright, you need real microphone permission. Stubbing getUserMedia with an AudioContext stream avoids the permission prompt, but Chrome then withholds host ICE candidates — ICE never leaves new, no return feed is ever sent, and you will "reproduce" a failure that is a sandbox artifact rather than this bug. CDP Browser.grantPermissions alone was not sufficient for me either.

Playwright sidesteps this with --use-fake-device-for-media-capture, which is why CI is the reliable environment.

Background

Full investigation notes, including the DevTools transceiver evidence and the run-by-run table: memory-bank/tasks/2026-09/260912_ci_flakiness.md.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the failure with Node 22 using tests/test-return-feed.mjs and the Playwright environment. Start at web/js/connection-manager.js:295-310, adding receive-path logging around the missing offer, and compare the behavior with Node 18 and 20; rtc-manager.js:169 and the existing transceiver fix are already ruled out. Done means the test passes reliably on all three Node versions and CI no longer uses continue-on-error.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js, playwright
Domain
audio-video-rtc, testing-qa
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
67/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.