Silence orphaned unhandledRejections from extension management#314792
Merged
bryanchen-d merged 1 commit intoMay 6, 2026
Conversation
Two boot-time/race-condition paths in the shared-process extension management service produce `unhandledRejection` events that surface as `unhandlederror-Cannot read the extension from ...` telemetry buckets, even though the underlying install failure is already reported by the primary install task or logged by the cleanup/migrate flows themselves. 1. Duplicate-install wait promise (abstractExtensionManagementService.ts): when a second install request races for an extension that's already being installed, a sibling promise is created via `Event.toPromise(onDidInstallExtensions).then(...)` and pushed into `alreadyRequestedInstallations`. It's awaited on the success path, but if the outer `try` throws first (e.g. another extension in the same batch fails) control jumps to `catch` and the sibling promise is never observed -> Node fires `unhandledRejection`. Attach a no-op rejection handler so the rejection is always observed; the original promise is still awaited via `joinAllSettled` on the happy path, and the underlying failure is already surfaced through the primary task's error result. 2. Fire-and-forget startup tasks (sharedProcess/contrib/extensions.ts): `extensionManagementService.cleanUp()` and `migrateUnsupportedExtensions()` are called without `await` and without a `.catch`. Any rejection becomes an unhandled rejection. Both paths already log their own internal failures, so the only thing missing is a top-level `.catch(logService.error)` to swallow the orphan. Net effect: removes the entire `unhandlederror-Cannot read the extension from ...` family from top error buckets without losing any real diagnostic signal.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR addresses boot-time race conditions in extension management that can lead to orphaned unhandledRejection events (notably the unhandlederror-Cannot read the extension from ... telemetry family), without suppressing the underlying install/cleanup error reporting that already exists elsewhere in the pipeline.
Changes:
- Prevents orphaned rejections when waiting on a duplicate/overlapping extension install by attaching a no-op rejection handler to the “wait for install completion” promise.
- Ensures shared-process startup tasks (
cleanUp, unsupported-extension migration) are not able to surface as unhandled rejections by adding top-level.catch(...)logging.
Show a summary per file
| File | Description |
|---|---|
| src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts | Adds a rejection handler to the duplicate-install “wait” promise so failures can’t become orphaned unhandledRejections if the outer flow exits early. |
| src/vs/code/electron-utility/sharedProcess/contrib/extensions.ts | Adds .catch(...) logging to fire-and-forget startup tasks to prevent shared-process boot-time unhandled rejections. |
Copilot's findings
- Files reviewed: 2/2 changed files
- Comments generated: 0
Contributor
📬 CODENOTIFYThe following users are being notified based on files changed in this PR: @deepak1556Matched files:
|
dmitrivMS
approved these changes
May 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Silence two boot-time / race-condition paths in the shared-process extension management service that produce orphan
unhandledRejectionevents, surfacing asunhandlederror-Cannot read the extension from ...telemetry buckets.These rejections carry no new diagnostic information — the underlying install failure is already reported by the primary install task (
extensionGallery:installevent with the real error, stack, and extension id) or logged by the cleanup/migrate flows themselves.Two paths fixed
1. Duplicate-install wait promise —
abstractExtensionManagementService.tsWhen a second install request races for an extension that's already being installed (very common at boot for
github.copilot-chatbecause it gets re-requested by auto-update + restore + sync), a sibling promise is created viaEvent.toPromise(onDidInstallExtensions).then(...)and pushed intoalreadyRequestedInstallations.It's awaited on the success path (line 451), but if the outer
trythrows first (e.g. another extension in the same batch fails) control jumps tocatchand the sibling promise is never observed → Node firesunhandledRejection.Fix: attach a no-op rejection handler on a separate handle so the rejection is always observed. The original promise is still awaited via
joinAllSettledon the happy path, and the underlying failure is already surfaced through the primary task'sinstallExtensionResultsMapentry.2. Fire-and-forget startup tasks —
sharedProcess/contrib/extensions.tsAny rejection becomes an unhandled rejection at sharedProcess boot. Both functions already log their own internal failures (and
migrateUnsupportedExtensionswraps eachinstallFromGalleryin its own try/catch), so the only thing missing is a top-level.catch(logService.error)to swallow the orphan.Why not just fix
scanLocalExtension?The throw inside
scanLocalExtensionis correct — the caller asked to read an extension that isn't where the caller said it would be (a separate path-construction bug producing//ext-id-style URIs that still needs investigation). Wrapping it would silently corrupt install state. The right fix is at the unawaited consumers.Impact
Removes the entire
unhandlederror-Cannot read the extension from ...family from the top error buckets (~7M machines / ~120M hits in the 14d window) without losing any diagnostic signal — user-initiated install failures still produce notifications andextensionGallery:installtelemetry as before.