Change:
VS Code Version: stable and from sources When launching the server and installing extensions using that have a common dependency, one of the extensions will fail activating, prompting to reload VS Code. For example installing, and contains which depends on also depends on installation task finishes first and then trying to activate fails as for installation which is part of installation task Steps to Reproduce: this branch in gitpod or codespaces will fail activating and a notification shows prompting to reload vscode <img width="448" alt="error_iamge" src="https://user-"cc
would you accept a PR fixing this? I took a look but it doesn't look something trivial to fix properly, I but I don't think is a good fix
PR would be always welcome. Agreed it is not trivial. Let's see if there is a simple fix that fixes this and won't cause any regressions.
Created a PR with a possible fix
ping, by any chance for July
I am sorry that I am on vacation so cannot be looked in July.
I was following this other , but it was marked as a duplicate and was therefore closed. This isse seems to be closed as well, although the issue persists. Specifically, it seems that VS Code Dev Containers installs extensions in alphabetical order, so that if an extension that comes later in the order is a pre-requisite for another that is earlier, then the installations will not behave as expected. Example, is a pre-requisite for , but comes later in alphabetical order. Thus, requires a VS Code Session restart after building the Dev Container. Is this issue fixed in VS Code Insiders, but not released yet?
The issue you reported is using the build 1.71. and this fix was pushed in 1.72.. So please update your VS Code and try.

Discussion:
import { Barrier, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationError, getErrorMessage } from 'vs/base/common/errors'; <del> import { Emitter } from 'vs/base/common/event'; </del> <ins> import { Emitter, Event } from 'vs/base/common/event'; </ins> import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri';

private extensionsControlManifest: Promise<IExtensionsControlManifest> | undefined; private lastReportTimestamp = 0; <del> private readonly installingExtensions = new Map<string, IInstallExtensionTask>(); </del> <ins> private readonly installingExtensions = new Map<string, { task: IInstallExtensionTask; waitingTasks: IInstallExtensionTask[] }>(); </ins> private readonly uninstallingExtensions = new Map<string, IUninstallExtensionTask>(); private readonly _onInstallExtension = this._register(new Emitter<ServerInstallExtensionEvent>());

) { super(); this._register(toDisposable(() => { <del> this.installingExtensions.forEach(task => task.cancel()); </del> <ins> this.installingExtensions.forEach(({ task }) => task.cancel()); </ins> this.uninstallingExtensions.forEach(promise => promise.cancel()); this.installingExtensions.clear(); this.uninstallingExtensions.clear();

// only cache gallery extensions tasks if (!URI.isUri(extension)) { <del> const installExtensionTask = this.installingExtensions.get(getInstallExtensionTaskKey(extension)); if (installExtensionTask) { </del> <ins> const installingExtension = this.installingExtensions.get(getInstallExtensionTaskKey(extension)); if (installingExtension) { </ins> this.logService.info('Extensions is already requested to install', extension.identifier.id); <del> const { local } = await installExtensionTask.waitUntilTaskIsFinished(); </del> <ins> const { local } = await installingExtension.task.waitUntilTaskIsFinished(); </ins> return local; } options = { ...options, installOnlyNewlyAddedFromExtensionPack: true /* always true for gallery extensions */ }; } const allInstallExtensionTasks: { task: IInstallExtensionTask; manifest: IExtensionManifest }[] = []; <ins> const alreadyRequestedInstallations: Promise<void>[] = []; </ins> const installResults: (ServerInstallExtensionResult & { local: ILocalExtension })[] = []; const installExtensionTask = this.createInstallExtensionTask(manifest, extension, options); if (!URI.isUri(extension)) { <del> this.installingExtensions.set(getInstallExtensionTaskKey(extension), installExtensionTask); </del> <ins> this.installingExtensions.set(getInstallExtensionTaskKey(extension), { task: installExtensionTask, waitingTasks: [] }); </ins> } this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension, profileLocation: options.profileLocation }); this.logService.info('Installing extension:', installExtensionTask.identifier.id);

this.logService.info('Installing the extension without checking dependencies and pack', installExtensionTask.identifier.id); } else { try { <del> const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensionsToInstall(installExtensionTask.identifier, manifest, !!options.installOnlyNewlyAddedFromExtensionPack, !!options.installPreReleaseVersion, options.profileLocation); </del> <ins> const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensions(installExtensionTask.identifier, manifest, !!options.installOnlyNewlyAddedFromExtensionPack, !!options.installPreReleaseVersion, options.profileLocation); const installed = await this.getInstalled(undefined, options.profileLocation); </ins> for (const { gallery, manifest } of allDepsAndPackExtensionsToInstall) { installExtensionHasDependents = installExtensionHasDependents || !!manifest.extensionDependencies?.some(id => areSameExtensions({ id }, installExtensionTask.identifier)); const key = getInstallExtensionTaskKey(gallery); <del> if (this.installingExtensions.has(key)) { this.logService.info('Extension is already requested to install', gallery.identifier.id); } else { </del> <ins> const existingInstallingExtension = this.installingExtensions.get(key); if (existingInstallingExtension) { if (this.canWaitForTask(installExtensionTask, existingInstallingExtension.task)) { this.logService.info('Waiting for already requested installing extension', gallery.identifier.id, installExtensionTask.identifier.id); existingInstallingExtension.waitingTasks.push(installExtensionTask); const identifier = existingInstallingExtension.task.identifier; // 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 => { 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))) { </ins> const task = this.createInstallExtensionTask(manifest, gallery, { ...options, donotIncludePackAndDependencies: true }); <del> this.installingExtensions.set(key, task); </del> <ins> this.installingExtensions.set(key, { task, waitingTasks: [installExtensionTask] }); </ins> this._onInstallExtension.fire({ identifier: task.identifier, source: gallery, profileLocation: options.profileLocation }); <del> this.logService.info('Installing extension:', task.identifier.id); </del> <ins> this.logService.info('Installing extension:', task.identifier.id, installExtensionTask.identifier.id); </ins> allInstallExtensionTasks.push({ task, manifest }); } }

})); } <ins> if (alreadyRequestedInstallations.length) { await this.joinAllSettled(alreadyRequestedInstallations); } </ins> installResults.forEach(({ identifier }) => this.logService.info(`Extension installed successfully:`, identifier.id)); this._onDidInstallExtensions.fire(installResults); return installResults.filter(({ identifier }) => areSameExtensions(identifier, installExtensionTask.identifier))[0].local;

this._onDidInstallExtensions.fire(allInstallExtensionTasks.map(({ task }) => ({ identifier: task.identifier, operation: InstallOperation.Install, source: task.source, context: options.context, profileLocation: options.profileLocation }))); throw error; } finally { <del> /* Remove the gallery tasks from the cache */ for (const { task } of allInstallExtensionTasks) { if (!URI.isUri(task.source)) { const key = getInstallExtensionTaskKey(task.source); if (!this.installingExtensions.delete(key)) { this.logService.warn('Installation task is not found in the cache', key); } </del> <ins> for (const [key, { task, waitingTasks }] of this.installingExtensions.entries()) { const index = waitingTasks.indexOf(installExtensionTask); if (index !== -1) { /* Current task was waiting for this task */ waitingTasks.splice(index, 1); } if (waitingTasks.length === 0 // No tasks are waiting for this task && (task === installExtensionTask || index !== -1)) { this.installingExtensions.delete(key); </ins> } } } } <ins> private canWaitForTask(taskToWait: IInstallExtensionTask, taskToWaitFor: IInstallExtensionTask): boolean { for (const [, { task, waitingTasks }] of this.installingExtensions.entries()) { // Cannot be waited, If taskToWaitFor is waiting for taskToWait if (task === taskToWait && waitingTasks.includes(taskToWaitFor)) { return false; } // Cannot be waited, if the taskToWait cannot be waited for the task created the taskToWaitFor if (task === taskToWaitFor && waitingTasks[0] && !this.canWaitForTask(taskToWait, waitingTasks[0])) { return false; } } return true; } </ins> private async joinAllSettled<T>(promises: Promise<T>[]): Promise<T[]> { const results: T[] = []; const errors: any[] = [];

return results; } <del> private async getAllDepsAndPackExtensionsToInstall(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean, installPreRelease: boolean, profile: URI | undefined): Promise<{ gallery: IGalleryExtension; manifest: IExtensionManifest }[]> { </del> <ins> private async getAllDepsAndPackExtensions(extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest, getOnlyNewlyAddedFromExtensionPack: boolean, installPreRelease: boolean, profile: URI | undefined): Promise<{ gallery: IGalleryExtension; manifest: IExtensionManifest }[]> { </ins> if (!this.galleryService.isEnabled()) { return []; } <del> let installed = await this.getInstalled(undefined, profile); const knownIdentifiers = [extensionIdentifier, ...(installed).map(i => i.identifier)]; </del> <ins> const installed = await this.getInstalled(undefined, profile); const knownIdentifiers = [extensionIdentifier]; </ins> const allDependenciesAndPacks: { gallery: IGalleryExtension; manifest: IExtensionManifest }[] = []; const collectDependenciesAndPackExtensionsToInstall = async (extensionIdentifier: IExtensionIdentifier, manifest: IExtensionManifest): Promise<void> => {

} if (dependenciesAndPackExtensions.length) { <del> // filter out installed and known extensions </del> <ins> // filter out known extensions </ins> const identifiers = [...knownIdentifiers, ...allDependenciesAndPacks.map(r => r.gallery.identifier)]; const ids = dependenciesAndPackExtensions.filter(id => identifiers.every(galleryIdentifier => !areSameExtensions(galleryIdentifier, { id }))); if (ids.length) {

}; await collectDependenciesAndPackExtensionsToInstall(extensionIdentifier, manifest); <del> installed = await this.getInstalled(undefined, profile); return allDependenciesAndPacks.filter(e => !installed.some(i => areSameExtensions(i.identifier, e.gallery.identifier))); </del> <ins> return allDependenciesAndPacks; </ins> } private async checkAndGetCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, installPreRelease: boolean): Promise<{ extension: IGalleryExtension; manifest: IExtensionManifest }> {