fix(extension-mgmt): record concurrent install result so callers don't see "Unknown error"#314868
Merged
bryanchen-d merged 2 commits intoMay 7, 2026
Merged
Conversation
…t see "Unknown error" When a concurrent installFromGallery for the same (extensionId, profileLocation) pair is already in flight, installExtensions takes the "wait for existing task" branch but never writes the wait result into installExtensionResultsMap. As a result installFromGallery sees an empty results array and throws the generic "Unknown error while installing extension X" - even when the in-flight install actually succeeded, and hiding the real failure cause when it didn't. Plumb the resolved ILocalExtension (or rejection reason) into installExtensionResultsMap using the same key shape as primary tasks so installFromGallery's lookup finds the real local / error. Fixes the symptom tracked in #310858 and #293368.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a race in AbstractExtensionManagementService.installExtensions(...) where concurrent installFromGallery calls could return an empty results array, causing callers to throw a generic Unknown error while installing extension <id> even when the underlying in-flight install actually succeeded or failed with a real error. The change ensures that when an install is already in progress, the waiting caller still records the resolved ILocalExtension or the normalized error into installExtensionResultsMap, allowing installFromGallery to surface the correct outcome.
Changes:
- When
this.installingExtensionsalready contains an in-flight task, attach handlers towaitUntilTaskIsFinished()and populateinstallExtensionResultsMapfor that caller. - Preserve existing rejection behavior by rethrowing after recording the error, so
joinAllSettledsemantics remain unchanged.
Show a summary per file
| File | Description |
|---|---|
| src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts | Records results for “already in-flight” installs so concurrent callers don’t fall back to the generic “Unknown error” path. |
Copilot's findings
- Files reviewed: 1/1 changed files
- Comments generated: 2
Mirror the existing pattern used for the inner waitForInstallation: assign the chained promise to a variable and attach a no-op .catch so the rejection is always observed even when the outer try throws before joinAllSettled(alreadyRequestedInstallations) is reached. Addresses Copilot review feedback on PR #314868.
sandy081
approved these changes
May 7, 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.
Summary
Fixes a race in
installFromGallerythat surfaces in telemetry asUnknown error while installing extension <id>for ~1,484 unique users in the last 14 days (bucket810ac6ce…,github.copilot-chat), plus three other extension-specific buckets.Linked issues: #310858 (open, currently auto-triaged with the same root-cause analysis), #293368 (closed for "not enough information" — this PR explains the missing piece).
Root cause
AbstractExtensionManagementService.installExtensions(...)keeps a class-level mapinstallingExtensionsof in-flight installs keyed by(extensionId, profileLocation). When a secondinstallFromGallery(extA)arrives while a first is still running, the top-level loop takes the "wait for existing task" branch:Only
createInstallExtensionTaskpopulates the localinstallingExtensionsMap, so the parallel-install loop and the deps loop both iterate over an empty map and nothing writes intoinstallExtensionResultsMap.After the awaited tasks complete, the function does:
Back in
installFromGallery:Two failure modes, one symptom:
joinAllSettledrethrows; the outercatchonly iterates the (empty)installingExtensionsMapso no real error is recorded; control falls through andreturn []→ caller throws the generic message instead of the underlying cause. (This is why [Unhandled Error] Unknown error while installing extension github.copilot-chat #293368 had no actionable info.)Fix
When the existing-task branch is taken, attach
.then(...)/.catch(...)towaitUntilTaskIsFinished()and write the resolvedILocalExtension(or rejection reason, normalized viatoExtensionManagementError) intoinstallExtensionResultsMapkeyed identically to primary tasks (${identifier.id.toLowerCase()}-${profileLocation.toString()}). On the failure path we still re-throw so the existingjoinAllSettledrejection / outercatchsemantics are preserved.After the fix:
installFromGallery'sresults.find(...)returns the realILocalExtension. ✅installFromGallerythrows the real error (e.g. network / signature / disk-full), which is whatsandy081needs to investigate. ✅No public API changes. ~28 lines net.
Telemetry context
810ac6ce…github.copilot-chat33a063ed…anthropic.claude-codefad68ea2…ms-ceintl.vscode-language-pack-zh-hans09e28363…ms-python.pythonAll four buckets share the exact stack frame at
abstractExtensionManagementService.ts:184(thethrow new ExtensionManagementError("Unknown error...")line).