Change:
Behaviour: Upon opening a GitHub code space with the following configuration of extensions, I get an error saying that one of the extensions are not installed (but it should be installed since it is in the list).VS Code Version: Version: 1.83.1 (Universal) / Commit: OS Version: macOS 13.6 (22G120) GitHub code spaces with the following .devcontainer (see below) the GitHub code space loaded you get the error: "Cannot activate the 'autopep8' extension because it depends on the 'Python' extension, which is not loaded. Would you like to reload the window to load the extension?" In speaking with the GitHub code spaces team, I learned that: "It looks like all of the extensions are being passed into the VS Code server at the same time, so it would require upstream changes in VS Code to handle the installation order in a better way." and "My guess is that the extensions are installed in parallel and the python one just takes longer to install/activate and the pep8 one is trying to activate too early before the python finishes installing" includes
I've also been seeing such issues for months, particularly for extensions that depend on . As someone maintaining the development setup environment for a team, it would be really helpful to have any solution that works without a manual reload or custom configuration for each user.
We have a similar issue with C#. If you install the C# extension or the C# DevKit extension, it will install quicker than it's dependent ".NET Install Tool" extension. This race condition leads to a prompt that shows up the majority of the time to reload the editor to install the dependent extension.
I have been running into this same situation, except inside a devcontainer. The Black Formatter extension and the Flake8 extension fail 100% of the time and VSCode suggests reloading. Reloading always works. Is anyone aware of a workaround in the meantime to enforce an ordering?
I stumbled upon a workaround with a long-running in , so I suspect for anyone having this issue you could potentially do something like: Not pretty, but neither is the "always click the reload button" option.
I'm seeing the same behavior as with a slight clarification: I have the following in my Order (moving python above black-formatter, flake8, and isort) doesn't seem to matter. Visual Studio Code Version: 1.84.2 (user setup) Commit: Date: 2023-11-09T10:51:52.184Z Electron: 25.9.2 ElectronBuildId: Chromium: 114.0.5735.289 18.15.0 V8: 11.4.183.29-electron.0 OS: Windows_NT x64 10.0.
This bug makes it rough to create a "minimal" template for .NET and Python developers in GitHub Codespaces with the .NET/Python extensions as it will almost always ask you to reopen the codespace. Even this configuration will require a "reopen" prompt: EDIT: Adding photos ! ! Something like an "extension-order" setting, similar to the features, would be amazing in VS Code or development containers.
Or at least loading extensions in the order as given in the list, then users could at least easily hand-sort to avoid these annoying dependency problems until a "real" fix comes along. Presumably/hopefully this could be a quick fix as it's a small change?
Can anyone reproducing this issue, please share following logs? F1 Open View... Server
use this link to open the  repository as a codespace: devcontainer configuration: waited for the C# extensions to install and to observe the "Cannot activate the 'C#' extension because it depends on the '.NET Install Tool' extension, which is not loaded. Would you like to reload the window to load the extension?" popup. 1 Open View Server 's the logs:
I repeated this experiment using a Codespace on a different repository and configuration file. Repository:  Codespaces link: Devcontainer configuration: I could not repro the same issue that observed. I may be missing an important step to repro.
Thanks for the log. From the log it seems following extensions are requested to be installed github.codespaces and following extensions are requested to be installed again ms-dotnettools.vscodeintellicode-csharp ms-dotnettools.csdevkit ms- ms- By any chance, you have settings sync enabled and you have above extensions synced?
I do have settings sync enabled with the C# and C# DevKit extensions.
Thank you for trying I tried your and also didn't see the issue. Given , maybe the codespace takes just long enough to load things before opening the window to avoid the Reload prompt? Although it does seem to be doing things in the right order ... this is the end of F1 Open View Server from that codespace I reproduced the issue in a smaller repository () via the Dev Containers and WSL extensions and the following container spec ! Help About: Version: 1.68.1 (user setup) Commit: Date: 2022-06-14T12:48:58.283Z Electron: 17.4.7 Chromium: 98.0.4758.141 16.13.0 V8: 9.8.177.13-electron.0 OS: Windows_NT x64 10.0. Note this is an older version of VS Code than I used above in F1 Open View Log (Remote Server) This is without Settings Sync enabled !
We closed this issue because we in the foreseeable future. If you disagree and feel that this issue is crucial: we are happy to listen and to reconsider. If you wonder what we are up to, please see our and . Thanks for your understanding, and happy coding!
I have to admit that I'm confused. The linked page talks about closing out-of-scope feature requests. I don't see how this can be relevant here, as this report is of extension loading being broken/erroring. In fact, I'm confused why the label was removed by Furthermore, users have supplied logs as requested, and even repos for reproduction, yet the label was not removed, and now failing extension loading is closed as ? I'd be curious about the decision process here, to be honest.
Yeah - I also disagree with closing as here. I'm continuing to see wierd bugs with extensions here - these are effective usability problems that cause friction and requires workarounds. Example with latest version of VSCode: Upon opening a GitHub code space with the following configuration of extensions, I get an error saying that one of the extensions are not installed (but it should be installed since it is in the list). GitHub code spaces with the following .devcontainer (see below) the GitHub code space loaded you get the error: "Cannot activate the 'autopep8' extension because it depends on the 'Python' extension, which is not loaded. Would you like to reload the window to load the extension?" Despite clearly declaring Python extension, I get this error in the log: Now if I close and reopen the codespace, I get thing working: and my poetry managed environment is available to Jupyter. It's really hard for my users to understand why they have to restart VSCode and reopen the codespace before the extension works.
I work at GitHub and I would be happy to jump on a call and walk you through how to reproduce this if you can't reproduce.
Thanks for feedback. I closed this because I do not see us investing resources in fixing issues like this in the next 6 months. Not saying it is invalid. We might still reopen it after I discuss with As for GitHub - I suggest to reach out to the Codespaces team and if they have capacity they can invest in making a PR.
any updates or ? I have another instance of this again: with clearly in the devcontainer ! with a of:
Will investigate and see if I can repro
! please see full server log here: With and latest version System info
this shows another simple repro example, they even provided a repo that reproduces this issue every time and it only has two extensions listed in
As a follow-up to luabuds post above, this is the logs from F1 Open View Server.
PR to fix:
thanks

Discussion:
IExtensionsControlManifest, StatisticType, isTargetPlatformCompatible, TargetPlatformToString, ExtensionManagementErrorCode, InstallOptions, InstallVSIXOptions, UninstallOptions, Metadata, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, UninstallExtensionEvent, IExtensionManagementService, InstallExtensionInfo, EXTENSION_INSTALL_DEP_PACK_CONTEXT, ExtensionGalleryError } from 'vs/platform/extensionManagement/common/extensionManagement'; <del> import { adoptToGalleryExtensionId, areSameExtensions, ExtensionKey, getGalleryExtensionId, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; </del> <ins> import { areSameExtensions, ExtensionKey, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; </ins> import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService';

protected async installExtensions(extensions: InstallableExtension[]): Promise<InstallExtensionResult[]> { const results: InstallExtensionResult[] = []; <del> await Promise.allSettled(extensions.map(async e => { try { const result = await this.installExtension(e, (taskToWait: IInstallExtensionTask, taskToWaitFor: IInstallExtensionTask): boolean => { if (extensions.some(e => adoptToGalleryExtensionId(taskToWaitFor.identifier.id) === getGalleryExtensionId(e.manifest.publisher, e.manifest.name))) { return false; } return this.canWaitForTask(taskToWait, taskToWaitFor); }); results.push(...result); } catch (error) { results.push({ identifier: { id: getGalleryExtensionId(e.manifest.publisher, e.manifest.name) }, operation: InstallOperation.Install, source: e.extension, error }); } })); this._onDidInstallExtensions.fire(results); return results; } private async installExtension({ manifest, extension, options }: InstallableExtension, shouldWait: (taskToWait: IInstallExtensionTask, taskToWaitFor: IInstallExtensionTask) => boolean): Promise<InstallExtensionResult[]> { const isApplicationScoped = options.isApplicationScoped || options.isBuiltin || isApplicationScopedExtension(manifest); const installExtensionTaskOptions: InstallExtensionTaskOptions = { ...options, installOnlyNewlyAddedFromExtensionPack: URI.isUri(extension) ? options.installOnlyNewlyAddedFromExtensionPack : true, /* always true for gallery extensions */ isApplicationScoped, profileLocation: isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation() </del> <ins> const installingExtensionsMap = new Map<string, { task: IInstallExtensionTask; manifest: IExtensionManifest }>(); const alreadyRequestedInstallations: Promise<any>[] = []; const successResults: (InstallExtensionResult & { local: ILocalExtension; profileLocation: URI })[] = []; const getInstallExtensionTaskKey = (extension: IGalleryExtension, profileLocation: URI) => `${ExtensionKey.create(extension).toString()}-${profileLocation.toString()}`; const createInstallExtensionTask = (manifest: IExtensionManifest, extension: IGalleryExtension | URI, options: InstallExtensionTaskOptions): void => { const installExtensionTask = this.createInstallExtensionTask(manifest, extension, options); const key = URI.isUri(extension) ? extension.path : `${extension.identifier.id.toLowerCase()}-${options.profileLocation.toString()}`; installingExtensionsMap.set(key, { task: installExtensionTask, manifest }); this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension, profileLocation: options.profileLocation }); this.logService.info('Installing extension:', installExtensionTask.identifier.id); // only cache gallery extensions tasks if (!URI.isUri(extension)) { this.installingExtensions.set(getInstallExtensionTaskKey(extension, options.profileLocation), { task: installExtensionTask, waitingTasks: [] }); } </ins> }; <del> const getInstallExtensionTaskKey = (extension: IGalleryExtension) => `${ExtensionKey.create(extension).toString()}${installExtensionTaskOptions.profileLocation ? `-${installExtensionTaskOptions.profileLocation.toString()}` : ''}`; // only cache gallery extensions tasks if (!URI.isUri(extension)) { const installingExtension = this.installingExtensions.get(getInstallExtensionTaskKey(extension)); if (installingExtension) { this.logService.info('Extensions is already requested to install', extension.identifier.id); await installingExtension.task.waitUntilTaskIsFinished(); return []; </del> <ins> try { // Start installing extensions for (const { manifest, extension, options } of extensions) { const isApplicationScoped = options.isApplicationScoped || options.isBuiltin || isApplicationScopedExtension(manifest); const installExtensionTaskOptions: InstallExtensionTaskOptions = { ...options, installOnlyNewlyAddedFromExtensionPack: URI.isUri(extension) ? options.installOnlyNewlyAddedFromExtensionPack : true, /* always true for gallery extensions */ isApplicationScoped, profileLocation: isApplicationScoped ? this.userDataProfilesService.defaultProfile.extensionsResource : options.profileLocation ?? this.getCurrentExtensionsManifestLocation() }; const existingInstallExtensionTask = !URI.isUri(extension) ? this.installingExtensions.get(getInstallExtensionTaskKey(extension, installExtensionTaskOptions.profileLocation)) : undefined; if (existingInstallExtensionTask) { this.logService.info('Extension is already requested to install', existingInstallExtensionTask.task.identifier.id); alreadyRequestedInstallations.push(existingInstallExtensionTask.task.waitUntilTaskIsFinished()); } else { createInstallExtensionTask(manifest, extension, installExtensionTaskOptions); } </ins> } <del> } const allInstallExtensionTasks: { task: IInstallExtensionTask; manifest: IExtensionManifest }[] = []; const alreadyRequestedInstallations: Promise<void>[] = []; const installResults: (InstallExtensionResult & { local: ILocalExtension })[] = []; const installExtensionTask = this.createInstallExtensionTask(manifest, extension, installExtensionTaskOptions); if (!URI.isUri(extension)) { this.installingExtensions.set(getInstallExtensionTaskKey(extension), { task: installExtensionTask, waitingTasks: [] }); } this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension, profileLocation: installExtensionTaskOptions.profileLocation }); this.logService.info('Installing extension:', installExtensionTask.identifier.id); allInstallExtensionTasks.push({ task: installExtensionTask, manifest }); let installExtensionHasDependents: boolean = false; try { if (installExtensionTaskOptions.donotIncludePackAndDependencies) { this.logService.info('Installing the extension without checking dependencies and pack', installExtensionTask.identifier.id); } else { try { const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(installExtensionTask.identifier, manifest, !!installExtensionTaskOptions.installOnlyNewlyAddedFromExtensionPack, !!installExtensionTaskOptions.installPreReleaseVersion, installExtensionTaskOptions.profileLocation); const installed = await this.getInstalled(undefined, installExtensionTaskOptions.profileLocation); const options: InstallExtensionTaskOptions = { ...installExtensionTaskOptions, donotIncludePackAndDependencies: true, context: { ...installExtensionTaskOptions.context, [EXTENSION_INSTALL_DEP_PACK_CONTEXT]: true } }; for (const { gallery, manifest } of distinct(allDepsAndPackExtensionsToInstall, ({ gallery }) => gallery.identifier.id)) { installExtensionHasDependents = installExtensionHasDependents || !!manifest.extensionDependencies?.some(id => areSameExtensions({ id }, installExtensionTask.identifier)); const key = getInstallExtensionTaskKey(gallery); const existingInstallingExtension = this.installingExtensions.get(key); if (existingInstallingExtension) { if (shouldWait(installExtensionTask, existingInstallingExtension.task)) { const identifier = existingInstallingExtension.task.identifier; this.logService.info('Waiting for already requested installing extension', identifier.id, installExtensionTask.identifier.id); existingInstallingExtension.waitingTasks.push(installExtensionTask); // add promise that waits until the extension is completely installed, ie., onDidInstallExtensions event is triggered for this extension alreadyRequestedInstallations.push( Event.toPromise( Event.filter(this.onDidInstallExtensions, results => results.some(result => areSameExtensions(result.identifier, identifier))) ).then(results => { this.logService.info('Finished waiting for already requested installing extension', identifier.id, installExtensionTask.identifier.id); const result = results.find(result => areSameExtensions(result.identifier, identifier)); if (!result?.local) { // Extension failed to install throw new Error(`Extension ${identifier.id} is not installed`); } })); </del> <ins> // collect and start installing all dependencies and pack extensions await Promise.all([...installingExtensionsMap.values()].map(async ({ task, manifest }) => { if (task.options.donotIncludePackAndDependencies) { this.logService.info('Installing the extension without checking dependencies and pack', task.identifier.id); } else { try { const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(task.identifier, manifest, !!task.options.installOnlyNewlyAddedFromExtensionPack, !!task.options.installPreReleaseVersion, task.options.profileLocation); const installed = await this.getInstalled(undefined, task.options.profileLocation); const options: InstallExtensionTaskOptions = { ...task.options, donotIncludePackAndDependencies: true, context: { ...task.options.context, [EXTENSION_INSTALL_DEP_PACK_CONTEXT]: true } }; for (const { gallery, manifest } of distinct(allDepsAndPackExtensionsToInstall, ({ gallery }) => gallery.identifier.id)) { if (installingExtensionsMap.has(`${gallery.identifier.id.toLowerCase()}-${options.profileLocation.toString()}`)) { continue; } const existingInstallingExtension = this.installingExtensions.get(getInstallExtensionTaskKey(gallery, options.profileLocation)); if (existingInstallingExtension) { if (this.canWaitForTask(task, existingInstallingExtension.task)) { const identifier = existingInstallingExtension.task.identifier; this.logService.info('Waiting for already requested installing extension', identifier.id, task.identifier.id); existingInstallingExtension.waitingTasks.push(task); // add promise that waits until the extension is completely installed, ie., onDidInstallExtensions event is triggered for this extension alreadyRequestedInstallations.push( Event.toPromise( Event.filter(this.onDidInstallExtensions, results => results.some(result => areSameExtensions(result.identifier, identifier))) ).then(results => { this.logService.info('Finished waiting for already requested installing extension', identifier.id, task.identifier.id); const result = results.find(result => areSameExtensions(result.identifier, identifier)); if (!result?.local) { // Extension failed to install throw new Error(`Extension ${identifier.id} is not installed`); } })); } } else if (!installed.some(({ identifier }) => areSameExtensions(identifier, gallery.identifier))) { createInstallExtensionTask(manifest, gallery, options); </ins> } <del> } else if (!installed.some(({ identifier }) => areSameExtensions(identifier, gallery.identifier))) { const task = this.createInstallExtensionTask(manifest, gallery, options); this.installingExtensions.set(key, { task, waitingTasks: [installExtensionTask] }); this._onInstallExtension.fire({ identifier: task.identifier, source: gallery, profileLocation: installExtensionTaskOptions.profileLocation }); this.logService.info('Installing extension:', task.identifier.id, installExtensionTask.identifier.id); allInstallExtensionTasks.push({ task, manifest }); } } } catch (error) { // Installing through VSIX if (URI.isUri(installExtensionTask.source)) { // Ignore installing dependencies and packs if (isNonEmptyArray(manifest.extensionDependencies)) { this.logService.warn(`Cannot install dependencies of extension:`, installExtensionTask.identifier.id, error.message); </del> } <del> if (isNonEmptyArray(manifest.extensionPack)) { this.logService.warn(`Cannot install packed extensions of extension:`, installExtensionTask.identifier.id, error.message); </del> <ins> } catch (error) { // Installing through VSIX if (URI.isUri(task.source)) { // Ignore installing dependencies and packs if (isNonEmptyArray(manifest.extensionDependencies)) { this.logService.warn(`Cannot install dependencies of extension:`, task.identifier.id, error.message); } if (isNonEmptyArray(manifest.extensionPack)) { this.logService.warn(`Cannot install packed extensions of extension:`, task.identifier.id, error.message); } } else { this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', task.identifier.id); throw error; </ins> } <del> } else { this.logService.error('Error while preparing to install dependencies and extension packs of the extension:', installExtensionTask.identifier.id); throw error; </del> } } <del> } const extensionsToInstallMap = allInstallExtensionTasks.reduce((result, { task, manifest }) => { result.set(task.identifier.id.toLowerCase(), { task, manifest }); return result; }, new Map<string, { task: IInstallExtensionTask; manifest: IExtensionManifest }>()); while (extensionsToInstallMap.size) { let extensionsToInstall; const extensionsWithoutDepsToInstall = [...extensionsToInstallMap.values()].filter(({ manifest }) => !manifest.extensionDependencies?.some(id => extensionsToInstallMap.has(id.toLowerCase()))); if (extensionsWithoutDepsToInstall.length) { extensionsToInstall = extensionsToInstallMap.size === 1 ? extensionsWithoutDepsToInstall /* If the main extension has no dependents remove it and install it at the end */ : extensionsWithoutDepsToInstall.filter(({ task }) => !(task === installExtensionTask && !installExtensionHasDependents)); } else { this.logService.info('Found extensions with circular dependencies', extensionsWithoutDepsToInstall.map(({ task }) => task.identifier.id)); extensionsToInstall = [...extensionsToInstallMap.values()]; } </del> <ins> })); </ins> <del> // Install extensions in parallel and wait until all extensions are installed / failed await this.joinAllSettled(extensionsToInstall.map(async ({ task }) => { const startTime = new Date().getTime(); try { const local = await task.run(); await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, installExtensionTaskOptions, CancellationToken.None))); if (!URI.isUri(task.source)) { const isUpdate = task.operation === InstallOperation.Update; const durationSinceUpdate = isUpdate ? undefined : (new Date().getTime() - task.source.lastUpdated) / 1000; reportTelemetry(this.telemetryService, isUpdate ? 'extensionGallery:update' : 'extensionGallery:install', { extensionData: getGalleryExtensionTelemetryData(task.source), verificationStatus: task.verificationStatus, duration: new Date().getTime() - startTime, durationSinceUpdate }); // In web, report extension install statistics explicitly. In Desktop, statistics are automatically updated while downloading the VSIX. if (isWeb && task.operation !== InstallOperation.Update) { try { await this.galleryService.reportStatistic(local.manifest.publisher, local.manifest.name, local.manifest.version, StatisticType.Install); } catch (error) { /* ignore */ } } </del> <ins> // Install extensions in parallel and wait until all extensions are installed / failed await this.joinAllSettled([...installingExtensionsMap.values()].map(async ({ task }) => { const startTime = new Date().getTime(); try { const local = await task.run(); await this.joinAllSettled(this.participants.map(participant => participant.postInstall(local, task.source, task.options, CancellationToken.None))); if (!URI.isUri(task.source)) { const isUpdate = task.operation === InstallOperation.Update; const durationSinceUpdate = isUpdate ? undefined : (new Date().getTime() - task.source.lastUpdated) / 1000; reportTelemetry(this.telemetryService, isUpdate ? 'extensionGallery:update' : 'extensionGallery:install', { extensionData: getGalleryExtensionTelemetryData(task.source), verificationStatus: task.verificationStatus, duration: new Date().getTime() - startTime, durationSinceUpdate }); // In web, report extension install statistics explicitly. In Desktop, statistics are automatically updated while downloading the VSIX. if (isWeb && task.operation !== InstallOperation.Update) { try { await this.galleryService.reportStatistic(local.manifest.publisher, local.manifest.name, local.manifest.version, StatisticType.Install); } catch (error) { /* ignore */ } </ins> } <ins> } </ins> <del> installResults.push({ local, identifier: task.identifier, operation: task.operation, source: task.source, context: task.options.context, profileLocation: task.profileLocation, applicationScoped: local.isApplicationScoped }); } catch (error) { this.logService.error('Error while installing the extension', task.identifier.id, getErrorMessage(error)); throw error; } finally { extensionsToInstallMap.delete(task.identifier.id.toLowerCase()); } })); } </del> <ins> successResults.push({ local, identifier: task.identifier, operation: task.operation, source: task.source, context: task.options.context, profileLocation: task.profileLocation, applicationScoped: local.isApplicationScoped }); } catch (error) { this.logService.error('Error while installing the extension', task.identifier.id, getErrorMessage(error)); throw error; } })); </ins> if (alreadyRequestedInstallations.length) { await this.joinAllSettled(alreadyRequestedInstallations); } <del> installResults.forEach(({ identifier }) => this.logService.info(`Extension installed successfully:`, identifier.id)); return installResults; </del> <ins> for (const result of successResults) { this.logService.info(`Extension installed successfully:`, result.identifier.id); results.push(result); } return results; </ins> } catch (error) { <del> // cancel all tasks allInstallExtensionTasks.forEach(({ task }) => task.cancel()); </del> // rollback installed extensions <del> if (installResults.length) { try { const result = await Promise.allSettled(installResults.map(({ local }) => this.createUninstallExtensionTask(local, { versionOnly: true, profileLocation: installExtensionTaskOptions.profileLocation }).run())); for (let index = 0; index < result.length; index++) { const r = result[index]; const { identifier } = installResults[index]; if (r.status === 'fulfilled') { this.logService.info('Rollback: Uninstalled extension', identifier.id); } else { this.logService.warn('Rollback: Error while uninstalling extension', identifier.id, getErrorMessage(r.reason)); } </del> <ins> if (successResults.length) { await Promise.allSettled(successResults.map(async ({ local, profileLocation }) => { try { await this.createUninstallExtensionTask(local, { versionOnly: true, profileLocation }).run(); this.logService.info('Rollback: Uninstalled extension', local.identifier.id); } catch (error) { this.logService.warn('Rollback: Error while uninstalling extension', local.identifier.id, getErrorMessage(error)); </ins> } <del> } catch (error) { // ignore error this.logService.warn('Error while rolling back extensions', getErrorMessage(error), installResults.map(({ identifier }) => identifier.id)); } </del> <ins> })); </ins> } <del> return allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: installExtensionTaskOptions.context, profileLocation: installExtensionTaskOptions.profileLocation, error })); </del> <ins> // cancel all tasks and collect error results for (const { task } of installingExtensionsMap.values()) { task.cancel(); results.push({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: task.options.context, profileLocation: task.profileLocation, error }); } throw error; </ins> } finally { // Finally, remove all the tasks from the cache <del> for (const { task } of allInstallExtensionTasks) { </del> <ins> for (const { task } of installingExtensionsMap.values()) { </ins> if (task.source && !URI.isUri(task.source)) { <del> this.installingExtensions.delete(getInstallExtensionTaskKey(task.source)); </del> <ins> this.installingExtensions.delete(getInstallExtensionTaskKey(task.source, task.profileLocation)); </ins> } } <ins> if (results.length) { this._onDidInstallExtensions.fire(results); } </ins> } }

const useId = localize('useId', "Make sure you use the full extension ID, including the publisher, e.g.: {0}", 'ms-dotnettools.csharp'); type InstallVSIXInfo = { vsix: URI; installOptions: InstallOptions }; <del> type CLIInstallExtensionInfo = { id: string; version?: string; installOptions: InstallOptions }; </del> <ins> type InstallGalleryExtensionInfo = { id: string; version?: string; installOptions: InstallOptions }; </ins> export class ExtensionManagementCLI {

const failed: string[] = []; try { <del> const installedExtensionsManifests: IExtensionManifest[] = []; </del> if (extensions.length) { this.logger.info(this.location ? localize('installingExtensionsOnLocation', "Installing extensions on {0}...", this.location) : localize('installingExtensions', "Installing extensions...")); } const installVSIXInfos: InstallVSIXInfo[] = []; <del> let installExtensionInfos: CLIInstallExtensionInfo[] = []; </del> <ins> const installExtensionInfos: InstallGalleryExtensionInfo[] = []; </ins> const addInstallExtensionInfo = (id: string, version: string | undefined, isBuiltin: boolean) => { installExtensionInfos.push({ id, version: version !== 'prerelease' ? version : undefined, installOptions: { ...installOptions, isBuiltin, installPreReleaseVersion: version === 'prerelease' || installOptions.installPreReleaseVersion } }); };

if (installVSIXInfos.length) { await Promise.all(installVSIXInfos.map(async ({ vsix, installOptions }) => { try { <del> const manifest = await this.installVSIX(vsix, installOptions, force, installed); if (manifest) { installedExtensionsManifests.push(manifest); } </del> <ins> await this.installVSIX(vsix, installOptions, force, installed); </ins> } catch (err) { this.logger.error(err); failed.push(vsix.toString());

} if (installExtensionInfos.length) { <del> installExtensionInfos = installExtensionInfos.filter(({ id, version }) => { const installedExtension = installed.find(i => areSameExtensions(i.identifier, { id })); if (installedExtension) { if (!force && (!version || (version === 'prerelease' && installedExtension.preRelease))) { this.logger.info(localize('alreadyInstalled-checkAndUpdate', "Extension '{0}' v{1} is already installed. Use '--force' option to update to latest version or provide '@<version>' to install a specific version, for example: '{2}@1.2.3'.", id, installedExtension.manifest.version, id)); return false; } if (version && installedExtension.manifest.version === version) { this.logger.info(localize('alreadyInstalled', "Extension '{0}' is already installed.", `${id}@${version}`)); return false; } } return true; }); if (installExtensionInfos.length) { const galleryExtensions = await this.getGalleryExtensions(installExtensionInfos); await Promise.all(installExtensionInfos.map(async extensionInfo => { const gallery = galleryExtensions.get(extensionInfo.id.toLowerCase()); if (gallery) { try { const manifest = await this.installFromGallery(extensionInfo, gallery, installed); if (manifest) { installedExtensionsManifests.push(manifest); } } catch (err) { this.logger.error(err.message || err.stack || err); failed.push(extensionInfo.id); } } else { this.logger.error(`${notFound(extensionInfo.version ? `${extensionInfo.id}@${extensionInfo.version}` : extensionInfo.id)}n${useId}`); failed.push(extensionInfo.id); } })); } </del> <ins> const failedGalleryExtensions = await this.installGalleryExtensions(installExtensionInfos, installed, force); failed.push(...failedGalleryExtensions); </ins> } } catch (error) { this.logger.error(localize('error while installing extensions', "Error while installing extensions: {0}", getErrorMessage(error)));

} } <del> private async installVSIX(vsix: URI, installOptions: InstallOptions, force: boolean, installedExtensions: ILocalExtension[]): Promise<IExtensionManifest | null> { </del> <ins> private async installGalleryExtensions(installExtensionInfos: InstallGalleryExtensionInfo[], installed: ILocalExtension[], force: boolean): Promise<string[]> { installExtensionInfos = installExtensionInfos.filter(({ id, version }) => { const installedExtension = installed.find(i => areSameExtensions(i.identifier, { id })); if (installedExtension) { if (!force && (!version || (version === 'prerelease' && installedExtension.preRelease))) { this.logger.info(localize('alreadyInstalled-checkAndUpdate', "Extension '{0}' v{1} is already installed. Use '--force' option to update to latest version or provide '@<version>' to install a specific version, for example: '{2}@1.2.3'.", id, installedExtension.manifest.version, id)); return false; } if (version && installedExtension.manifest.version === version) { this.logger.info(localize('alreadyInstalled', "Extension '{0}' is already installed.", `${id}@${version}`)); return false; } } return true; }); if (!installExtensionInfos.length) { return []; } const failed: string[] = []; const extensionsToInstall: InstallExtensionInfo[] = []; const galleryExtensions = await this.getGalleryExtensions(installExtensionInfos); await Promise.all(installExtensionInfos.map(async ({ id, version, installOptions }) => { const gallery = galleryExtensions.get(id.toLowerCase()); if (!gallery) { this.logger.error(`${notFound(version ? `${id}@${version}` : id)}n${useId}`); failed.push(id); return; } try { const manifest = await this.extensionGalleryService.getManifest(gallery, CancellationToken.None); if (manifest && !this.validateExtensionKind(manifest)) { return; } } catch (err) { this.logger.error(err.message || err.stack || err); failed.push(id); return; } const installedExtension = installed.find(e => areSameExtensions(e.identifier, gallery.identifier)); if (installedExtension) { if (gallery.version === installedExtension.manifest.version) { this.logger.info(localize('alreadyInstalled', "Extension '{0}' is already installed.", version ? `${id}@${version}` : id)); return; } this.logger.info(localize('updateMessage', "Updating the extension '{0}' to the version {1}", id, gallery.version)); } if (installOptions.isBuiltin) { this.logger.info(version ? localize('installing builtin with version', "Installing builtin extension '{0}' v{1}...", id, version) : localize('installing builtin ', "Installing builtin extension '{0}'...", id)); } else { this.logger.info(version ? localize('installing with version', "Installing extension '{0}' v{1}...", id, version) : localize('installing', "Installing extension '{0}'...", id)); } extensionsToInstall.push({ extension: gallery, options: { ...installOptions, installGivenVersion: !!version }, }); })); if (extensionsToInstall.length) { const installationResult = await this.extensionManagementService.installGalleryExtensions(extensionsToInstall); for (const extensionResult of installationResult) { if (extensionResult.error) { this.logger.error(localize('errorInstallingExtension', "Error while installing extension {0}: {1}", extensionResult.identifier.id, getErrorMessage(extensionResult.error))); failed.push(extensionResult.identifier.id); } else { this.logger.info(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", extensionResult.identifier.id, extensionResult.local?.manifest.version)); } } } return failed; } private async installVSIX(vsix: URI, installOptions: InstallOptions, force: boolean, installedExtensions: ILocalExtension[]): Promise<void> { </ins> const manifest = await this.extensionManagementService.getManifest(vsix); if (!manifest) {

try { await this.extensionManagementService.install(vsix, installOptions); this.logger.info(localize('successVsixInstall', "Extension '{0}' was successfully installed.", basename(vsix))); <del> return manifest; </del> } catch (error) { if (isCancellationError(error)) { this.logger.info(localize('cancelVsixInstall', "Cancelled installing extension '{0}'.", basename(vsix))); <del> return null; </del> } else { throw error; } } } <del> return null; </del> } <del> private async getGalleryExtensions(extensions: CLIInstallExtensionInfo[]): Promise<Map<string, IGalleryExtension>> { </del> <ins> private async getGalleryExtensions(extensions: InstallGalleryExtensionInfo[]): Promise<Map<string, IGalleryExtension>> { </ins> const galleryExtensions = new Map<string, IGalleryExtension>(); const preRelease = extensions.some(e => e.installOptions.installPreReleaseVersion); const targetPlatform = await this.extensionManagementService.getTargetPlatform();

return galleryExtensions; } <del> private async installFromGallery({ id, version, installOptions }: CLIInstallExtensionInfo, galleryExtension: IGalleryExtension, installed: ILocalExtension[]): Promise<IExtensionManifest | null> { const manifest = await this.extensionGalleryService.getManifest(galleryExtension, CancellationToken.None); if (manifest && !this.validateExtensionKind(manifest)) { return null; } const installedExtension = installed.find(e => areSameExtensions(e.identifier, galleryExtension.identifier)); if (installedExtension) { if (galleryExtension.version === installedExtension.manifest.version) { this.logger.info(localize('alreadyInstalled', "Extension '{0}' is already installed.", version ? `${id}@${version}` : id)); return null; } this.logger.info(localize('updateMessage', "Updating the extension '{0}' to the version {1}", id, galleryExtension.version)); } try { if (installOptions.isBuiltin) { this.logger.info(version ? localize('installing builtin with version', "Installing builtin extension '{0}' v{1}...", id, version) : localize('installing builtin ', "Installing builtin extension '{0}'...", id)); } else { this.logger.info(version ? localize('installing with version', "Installing extension '{0}' v{1}...", id, version) : localize('installing', "Installing extension '{0}'...", id)); } const local = await this.extensionManagementService.installFromGallery(galleryExtension, { ...installOptions, installGivenVersion: !!version }); this.logger.info(localize('successInstall', "Extension '{0}' v{1} was successfully installed.", id, local.manifest.version)); return manifest; } catch (error) { if (isCancellationError(error)) { this.logger.info(localize('cancelInstall', "Cancelled installing extension '{0}'.", id)); return null; } else { throw error; } } } </del> protected validateExtensionKind(_manifest: IExtensionManifest): boolean { return true; }