You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An accessibility signal sound fails to play, and the error event handler in doPlayAudio rejects the promise with e.error — but a DOM media error event is a plain Event that has no error property, so the promise rejects with undefined. That undefined then reaches the catch (e) block in playSound, which reads e.message and throws TypeError: Cannot read properties of undefined (reading 'message'). Impact: an unhandled error is logged to telemetry whenever an accessibility cue sound fails to load/play (e.g. missing/blocked media), affecting 425 users on stable 1.140.0.
The faulty reject(e.error) has existed since the file was created; no recent commit changed the triggering logic. This bucket is new on the 1.140.0 stable channel but the defect is long-standing (surfaced at stable scale / under conditions where a cue sound fails to play). This is best characterized as a pre-existing bug newly observed, not a code regression.
No culprit commit identified: the defective line predates the available
history for this file and no commit in the regression window altered the
audio error-handling path. The fix targets the long-standing producer bug.
Code Flow
sequenceDiagram
participant Caller as playSignal
participant Sound as playSound
participant Audio as doPlayAudio
participant DOM as HTMLAudioElement
Caller->>Sound: await playSound(sound)
Sound->>Audio: await playAudio(url, volume)
Audio->>DOM: new Audio(url); audio.play()
DOM-->>Audio: 'error' event (plain Event, no .error)
Note over Audio: ⚠️ Root cause (L297):<br/>reject(e.error) → rejects with undefined
Audio-->>Sound: promise rejects with undefined
Note over Sound: 💥 L211: e.message<br/>Cannot read properties of<br/>undefined (reading 'message')
L211 (from stack): if (!e.message.includes(...)) reads .message on the undefined rejection value
Repro Steps
Trigger any accessibility signal that plays a sound (e.g. enable sounds via accessibility.signalOptions, then perform an action that plays a cue).
Cause the audio element to emit an error event — e.g. the media resource fails to load, is blocked, or is otherwise unplayable (the browser/Electron fires error rather than resolving play()).
The error handler rejects the play promise with undefined; the catch (e) in playSound reads e.message and throws TypeError: Cannot read properties of undefined (reading 'message'), which is reported as an unhandled error.
Because play()'s own rejection path (L300) rejects with a real Error, the crash only manifests via the media error event path, which explains the intermittent, environment-dependent occurrence.
How the Fix Works
Chosen approach (accessibilitySignalService.ts → doPlayAudio): fix the data producer, not the crash site. The error-event handler no longer rejects with the non-existent e.error. Instead it constructs a proper Error, enriched with the real failure details from audio.error (an HTMLMediaElement's MediaError, carrying .code and .message) when available, and a generic "Failed to play audio" message otherwise. After this change the promise always rejects with a genuine Error instance, so the downstream catch (e) in playSound can always read e.message safely — the TypeError can no longer be produced.
This keeps the existing logService/console.error telemetry path intact (the catch block still runs and still logs genuine failures), and it enriches the error with cross-boundary diagnostic context (MediaError.code) rather than swallowing it.
Alternatives considered:
Guard at the crash site (e?.message or if (!e) return in playSound's catch) — rejected: that is a crash-site guard that masks the producer bug, leaves every other consumer of playAudio receiving a meaningless undefined rejection, and discards the real failure cause.
Wrap the play call in try/catch that swallows the error — rejected: it would hide genuine audio failures from the telemetry pipeline.
Recommended Owner
@meganrogge — area owner of accessibilitySignalService.ts; authored the large majority of this file's history and is highly active in microsoft/vscode (hundreds of commits in the last 90 days). The culprit is pre-existing, so ownership falls to the feature-area owner.
Original error: ERR_API: [2026-10-01T15:40:49.269Z] 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: reject with proper Error on audio error event (fixes #339115)" --base main --head vscodebot-pr:fix/accessibility-signal-undefined-error-339115-fd9bae4896d46f3c --repo microsoft/vscode
Show patch (36 lines)
From 10fad800e35a5532e8680893099e22d61a9bf2d8 Mon Sep 17 00:00:00 2001
X-GH-AW-Base-Commit: 69ae25e227153d7795e539a54386b0c82988745f
From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com>
Date: Thu, 1 Oct 2026 15:32:41 +0000
Subject: [PATCH] fix: reject with proper Error on audio error event (fixes
#339115)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../browser/accessibilitySignalService.ts | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts
index 3576d7b9ba9..51b5647bb90 100644
--- a/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts+++ b/src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts@@ -294,9 +294,12 @@ function doPlayAudio(url: string, volume: number, disposables: DisposableStore):
disposables.add(addDisposableListener(audio, 'ended', () => {
resolve(audio);
}));
- disposables.add(addDisposableListener(audio, 'error', (e) => {- // When the error event fires, ended might not be called- reject(e.error);+ disposables.add(addDisposableListener(audio, 'error', () => {+ // When the error event fires, ended might not be called.+ // The media `error` event is a plain Event without an `error`+ // property; the actual failure is described by `audio.error`.+ const mediaError = audio.error;+ reject(new Error(mediaError ? `Failed to play audio (code ${mediaError.code}): ${mediaError.message}` : 'Failed to play audio'));
}));
audio.play().catch(e => {
// When play fails, the error event is not fired.
--
2.54.0
Summary
An accessibility signal sound fails to play, and the
errorevent handler indoPlayAudiorejects the promise withe.error— but a DOM mediaerrorevent is a plainEventthat has noerrorproperty, so the promise rejects withundefined. Thatundefinedthen reaches thecatch (e)block inplaySound, which readse.messageand throwsTypeError: Cannot read properties of undefined (reading 'message'). Impact: an unhandled error is logged to telemetry whenever an accessibility cue sound fails to load/play (e.g. missing/blocked media), affecting 425 users on stable 1.140.0.Fixes #339115
Recommended reviewer:
@meganroggeCulprit Commit
reject(e.error)has existed since the file was created; no recent commit changed the triggering logic. This bucket is new on the 1.140.0 stable channel but the defect is long-standing (surfaced at stable scale / under conditions where a cue sound fails to play). This is best characterized as a pre-existing bug newly observed, not a code regression.Code Flow
sequenceDiagram participant Caller as playSignal participant Sound as playSound participant Audio as doPlayAudio participant DOM as HTMLAudioElement Caller->>Sound: await playSound(sound) Sound->>Audio: await playAudio(url, volume) Audio->>DOM: new Audio(url); audio.play() DOM-->>Audio: 'error' event (plain Event, no .error) Note over Audio: ⚠️ Root cause (L297):<br/>reject(e.error) → rejects with undefined Audio-->>Sound: promise rejects with undefined Note over Sound: 💥 L211: e.message<br/>Cannot read properties of<br/>undefined (reading 'message')Affected Files
src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.tsreject(e.error)— the mediaerrorevent is a plainEventwith no.errorproperty, so the promise rejects withundefinedsrc/vs/platform/accessibilitySignal/browser/accessibilitySignalService.tsif (!e.message.includes(...))reads.messageon theundefinedrejection valueRepro Steps
accessibility.signalOptions, then perform an action that plays a cue).errorevent — e.g. the media resource fails to load, is blocked, or is otherwise unplayable (the browser/Electron fireserrorrather than resolvingplay()).errorhandler rejects the play promise withundefined; thecatch (e)inplaySoundreadse.messageand throwsTypeError: Cannot read properties of undefined (reading 'message'), which is reported as an unhandled error.Because
play()'s own rejection path (L300) rejects with a realError, the crash only manifests via the mediaerrorevent path, which explains the intermittent, environment-dependent occurrence.How the Fix Works
Chosen approach (
accessibilitySignalService.ts→doPlayAudio): fix the data producer, not the crash site. Theerror-event handler no longer rejects with the non-existente.error. Instead it constructs a properError, enriched with the real failure details fromaudio.error(anHTMLMediaElement'sMediaError, carrying.codeand.message) when available, and a generic"Failed to play audio"message otherwise. After this change the promise always rejects with a genuineErrorinstance, so the downstreamcatch (e)inplaySoundcan always reade.messagesafely — theTypeErrorcan no longer be produced.This keeps the existing
logService/console.errortelemetry path intact (thecatchblock still runs and still logs genuine failures), and it enriches the error with cross-boundary diagnostic context (MediaError.code) rather than swallowing it.Alternatives considered:
e?.messageorif (!e) returninplaySound's catch) — rejected: that is a crash-site guard that masks the producer bug, leaves every other consumer ofplayAudioreceiving a meaninglessundefinedrejection, and discards the real failure cause.Recommended Owner
@meganrogge— area owner ofaccessibilitySignalService.ts; authored the large majority of this file's history and is highly active inmicrosoft/vscode(hundreds of commits in the last 90 days). The culprit is pre-existing, so ownership falls to the feature-area owner.Note
This was originally intended as a pull request, but PR creation failed. The changes have been pushed to the branch
fix/accessibility-signal-undefined-error-339115-fd9bae4896d46f3c.Original error: ERR_API: [2026-10-01T15:40:49.269Z] 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: reject with proper Error on audio error event (fixes #339115)" --base main --head vscodebot-pr:fix/accessibility-signal-undefined-error-339115-fd9bae4896d46f3c --repo microsoft/vscodeShow patch (36 lines)