Skip to content

fix: skip real AudioContext in Voice Mode onboarding preview under tests (build fix for vscode-engineering#3742) #333856

Description

@vs-code-engineering

Build failure

The Run unit tests (Electron) task on the macOS insider stage hung and hit the task timeout right after the Voice Mode onboarding suite. The runner emitted a burst of [WARNING:services/audio/sync_reader.cc] SyncReader::Read timed out, audio glitch count=... messages, then produced no further output for ~14 minutes until it was killed, so .build/logs was never written and the downstream publish/verify steps cascaded.

Root cause

VoiceSamplePlayer.ensureAudio() in src/vs/workbench/contrib/agentsVoice/browser/voiceModeOnboarding.ts always constructs a real AudioContext and connects the analyser to context.destination:

const context = new targetWindow.AudioContext();
const analyser = context.createAnalyser();
context.createMediaElementSource(audio).connect(analyser);
analyser.connect(context.destination);

The onboarding tests inject a deterministic audio element via the audioFactory option, but nothing suppresses the real AudioContext. Connecting the analyser to context.destination opens the host's real audio output device. In headless Electron that device keeps polling for audio with no live producer, which is exactly what the repeated SyncReader::Read timed out glitch warnings report. Combined with the sample-playback tests (clicking the playing voice..., previews the native voice..., swaps the chips...) that each trigger a preview, the open output stream stalls the runner and the whole test task times out.

The AudioContext graph is best-effort waveform decoration only; the sample plays regardless. It was safe in interactive use but unsafe in headless CI, and there was no way for a test to opt out.

How the fix works

When a caller supplies its own audioFactory (only tests do), ensureAudio() now returns the injected element immediately and skips building the AudioContext/analyser graph. No real audio output device is opened, so the SyncReader polling loop never starts and the runner no longer stalls. Production code paths, which never pass audioFactory, are completely unchanged and still get the waveform analyser. The guard reuses the existing audioFactory field, adds no new types or imports, and preserves all existing logging.

Rollback evaluation

  • Recommendation: Do not roll back.
  • Public culprit commit/PR: not confirmed. The pre-existing AudioContext/destination graph was introduced earlier (in the initial Voice Mode onboarding change) and is the mechanism of the hang; the change that immediately preceded the failing build reworked the voice test setup and surfaced the stall, but source inspection does not establish it as the code-level cause.
  • Rationale: the fix-forward is a bounded, test-only guard with no production behavior change, so it is lower risk than reverting any recent voice work and does not require owner confirmation of revert safety.
  • Owners to consult: @meganrogge

Validation

Static source inspection only. The repository dependencies were not installed in this environment (node_modules absent), so npm run typecheck-client and the Electron unit test task could not be run here. The change is a single early-return guard using an existing field with no new imports or types. Reviewers should run the agentsVoice browser tests (voiceModeOnboarding.test.ts) in Electron to confirm the suite no longer hangs and emits no SyncReader::Read timed out warnings.

Risk

Low. The behavioral change is limited to callers that pass an audioFactory override, which is only the tests. Interactive Voice Mode onboarding still constructs the analyser graph and renders the live waveform exactly as before.

Recommended reviewer

Recommended owner: @meganrogge

Fixes microsoft/vscode-engineering#3742

Generated by build-fix · opus48 · 209 AIC · ⌖ 19 AIC · ⊞ 11.7K ·


Note

This was originally intended as a pull request, but PR creation failed. The changes have been pushed to the branch fix-voice-onboarding-audiocontext-test-hang-e9858b67fe5aa859.

Original error: ERR_API: [2026-09-01T20:09:38.640Z] create pull request in microsoft/vscode failed (attempt 1)

Original error: Validation Failed: {"resource":"PullRequest","code":"custom","field":"fork_collab","message":"fork_collab Fork collab can't be granted by someone without permission"} - https://docs.github.com/rest/pulls/pulls#create-a-pull-request
Retryable: false
Suggestion: This error cannot be resolved by retrying. Please check the error details and fix the underlying issue.

To create the pull request manually:

gh pr create --title "fix: skip real AudioContext in Voice Mode onboarding preview under tests (build fix for vscode-engineering#3742)" --base main --head vscodebot-pr:fix-voice-onboarding-audiocontext-test-hang-e9858b67fe5aa859 --repo microsoft/vscode
Show patch preview (44 of 44 lines)
From 7d50358fc4b2d388b16bf129469456a8610a90bd Mon Sep 17 00:00:00 2001
X-GH-AW-Base-Commit: 9fefe249cb7b3b373294607eb814177ff6ca0cf9
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Tue, 1 Sep 2026 20:02:25 +0000
Subject: [PATCH] fix: skip real AudioContext in Voice Mode onboarding preview
 under tests

The onboarding voice-sample preview always built a real AudioContext and
connected the analyser to context.destination, opening the host audio
output device even when a test-supplied audio element was injected. In
headless Electron test runs this device kept spinning with no producer,
emitting endless `SyncReader::Read timed out` audio-glitch warnings and
hanging the unit test task until timeout. Skip the best-effort analyser
graph when an audioFactory override is provided; the sample still plays.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
 .../contrib/agentsVoice/browser/voiceModeOnboarding.ts   | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/src/vs/workbench/contrib/agentsVoice/browser/voiceModeOnboarding.ts b/src/vs/workbench/contrib/agentsVoice/browser/voiceModeOnboarding.ts
index 93f8f9184ea..e2a653a5083 100644
--- a/src/vs/workbench/contrib/agentsVoice/browser/voiceModeOnboarding.ts
+++ b/src/vs/workbench/contrib/agentsVoice/browser/voiceModeOnboarding.ts
@@ -618,6 +618,15 @@ class VoiceSamplePlayer extends Disposable {
 			audio.src = '';
 		}));
 
+		// The analyser graph opens a real audio output device via
+		// `AudioContext`/`destination`. When a caller supplies its own audio
+		// element (only tests do) that device would keep spinning with no input,
+		// stalling headless runners with `SyncReader::Read timed out` warnings, so
+		// skip the best-effort waveform analysis - the sample still plays.
+		if (this.audioFactory) {
+			return audio;
+		}
+
 		try {
 			const context = new targetWindow.AudioContext();
 			this._register(toDisposable(() => void context.close().catch(() =>
... (truncated)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions