Change:
All of a sudden, I no longer have GHPRI installed. When I go into I see Settings Sync removed it 11 hours ago, but I'm 100% sure I never uninstalled it. I know I have Settings Sync enabled in both and are those scenarios doing unexpected things to my settings sync? !
I am sure that 4 days ago I re- the extension. Today, I'm on another machine, and GHPRI is again gone.
Lets sync on this
Closing as I couldn't repro.
I was able to reproduce this issue. will sometimes remove and then readd the GitHub Repositories, Issues & PR, and remotehub extensions, except it doesn't readd the keypair to the config, so when I return to my local installation the extensions will sometimes disappear. Not sure why it doesn't always reinstall though. Sometimes won't readd the missing extensions in time, so it gets removed. When the extensions are without the keypair, the local machine can occasionally readd the missing config and keep the extension installed; other times it won't and the extension is removed. Local machine logs from yesterday: Extension config history: Remote removes extensions: Remote readds extensions, but not marked as installed: After I reinstall the extensions locally: logs from a repro today: Extension sync history: Removed extensions: Readd extensions:
Thanks for logs and info. Reopening for investigation.
I am able to reproduce this with following steps: 1: Install GHPRI pre-release extension and sync .dev: Trigger Sync. Note that this is a builtin extension here. 2: Trigger Sync - Make sure GHPR extension is installed. 1: Uninstall GHPRI extension 2: Trigger Sync - Make sure GHPR extension is uninstalled. .dev: Trigger Sync. Make sure GHPRI extension exists in builtin extensions. 1: Trigger Sync. Nothing changes. 2: Trigger Sync. Nothing changes. This step and above step are important because, even though nothing has changed, these machines update their last sync state to have GHPRI extension has builtin extension. 1: Install GHPRI pre-release extension and sync 2: Trigger Sync GHPRI extension is not installed. Come back to Desktop 1: GHPRI extension is removed.
Improved extensions merging to make sure the builtin extensions are handled properly. Each machine remembers what builtin extensions it syncs and make sure it won't remove them while syncing. Also some logs to get more information if the issue still occurs.
If I uninstall GHPRI from desktop and then Sync Now in , the extension appears to be 'uninstalled' on , is that expected? ! After reloading the extension is still installed though.
I don't have a second desktop to verify with at the moment.
Yeah in your case it is expected because, GHPRI extension seems to be user installed and settings sync is removing that.

Discussion:
newRemoteExtensionsMap.set(key, merge(key, localExtension, remoteExtension, localExtension)); } <del> // Locally removed extensions => exist in base and does not exit in local </del> <ins> // Locally removed extensions => exist in base and does not exist in local </ins> for (const key of baseToLocal.removed.values()) { // If updated in remote (already handled) if (baseToRemote.updated.has(key)) {

if (extensions.length) { extensions = await this.checkAdditionalBuiltinExtensions(extensions); } <ins> if (extensions.length) { this.logService.info('Found additional builtin gallery extensions in env', extensions); } if (extensionLocations.length) { this.logService.info('Found additional builtin location extensions in env', extensionLocations.map(e => e.toString())); } </ins> return { extensions, extensionsToMigrate, extensionLocations }; })(); }

const extension = await this.toScannedExtension(webExtension, true); if (extension.isValid || !scanOptions?.skipInvalidExtensions) { result.push(extension); <ins> } else { this.logService.info(`Skipping invalid additional builtin extension ${webExtension.identifier.id}`); </ins> } } catch (error) { this.logService.info(`Error while fetching the additional builtin extension ${location.toString()}.`, getErrorMessage(error));

} private async getCustomBuiltinExtensionsFromGallery(scanOptions?: ScanOptions): Promise<IScannedExtension[]> { <del> const { extensions } = await this.readCustomBuiltinExtensionsInfoFromEnv(); if (!extensions.length) { return []; } </del> if (!this.galleryService.isEnabled()) { this.logService.info('Ignoring fetching additional builtin extensions from gallery as it is disabled.'); return []; } const result: IScannedExtension[] = []; <ins> const { extensions } = await this.readCustomBuiltinExtensionsInfoFromEnv(); </ins> try { const useCache = this.storageService.get('additionalBuiltinExtensions', StorageScope.APPLICATION, '[]') === JSON.stringify(extensions); const webExtensions = await (useCache ? this.getCustomBuiltinExtensionsFromCache() : this.updateCustomBuiltinExtensionsCache());

const extension = await this.toScannedExtension(webExtension, true); if (extension.isValid || !scanOptions?.skipInvalidExtensions) { result.push(extension); <ins> } else { this.logService.info(`Skipping invalid additional builtin gallery extension ${webExtension.identifier.id}`); </ins> } } catch (error) { this.logService.info(`Ignoring additional builtin extension ${webExtension.identifier.id} because there is an error while converting it into scanned extension`, getErrorMessage(error));

private async updateCustomBuiltinExtensionsCache(): Promise<IWebExtension[]> { if (!this._updateCustomBuiltinExtensionsCachePromise) { this._updateCustomBuiltinExtensionsCachePromise = (async () => { <del> // Clear Cache await this.writeCustomBuiltinExtensionsCache(() => []); const { extensions } = await this.readCustomBuiltinExtensionsInfoFromEnv(); if (!extensions.length) { return []; } const galleryExtensionsMap = await this.getExtensionsWithDependenciesAndPackedExtensions(extensions); const missingExtensions = extensions.filter(({ id }) => !galleryExtensionsMap.has(id.toLowerCase())); if (missingExtensions.length) { this.logService.info('Skipping the additional builtin extensions because their compatible versions are not found.', missingExtensions); } </del> <ins> this.logService.info('Updating additional builtin extensions cache'); </ins> const webExtensions: IWebExtension[] = []; <del> await Promise.all([...galleryExtensionsMap.values()].map(async gallery => { try { webExtensions.push(await this.toWebExtensionFromGallery(gallery, { isPreReleaseVersion: gallery.properties.isPreReleaseVersion, preRelease: gallery.properties.isPreReleaseVersion, isBuiltin: true })); } catch (error) { this.logService.info(`Ignoring additional builtin extension ${gallery.identifier.id} because there is an error while converting it into web extension`, getErrorMessage(error)); </del> <ins> const { extensions } = await this.readCustomBuiltinExtensionsInfoFromEnv(); if (extensions.length) { const galleryExtensionsMap = await this.getExtensionsWithDependenciesAndPackedExtensions(extensions); const missingExtensions = extensions.filter(({ id }) => !galleryExtensionsMap.has(id.toLowerCase())); if (missingExtensions.length) { this.logService.info('Skipping the additional builtin extensions because their compatible versions are not found.', missingExtensions); </ins> } <del> })); </del> <ins> await Promise.all([...galleryExtensionsMap.values()].map(async gallery => { try { webExtensions.push(await this.toWebExtensionFromGallery(gallery, { isPreReleaseVersion: gallery.properties.isPreReleaseVersion, preRelease: gallery.properties.isPreReleaseVersion, isBuiltin: true })); } catch (error) { this.logService.info(`Ignoring additional builtin extension ${gallery.identifier.id} because there is an error while converting it into web extension`, getErrorMessage(error)); } })); } </ins> await this.writeCustomBuiltinExtensionsCache(() => webExtensions); return webExtensions; })();

const extension = await this.toScannedExtension(webExtension, false); if (extension.isValid || !scanOptions?.skipInvalidExtensions) { result.set(extension.identifier.id.toLowerCase(), extension); <ins> } else { this.logService.info(`Skipping invalid installed extension ${webExtension.identifier.id}`); </ins> } } return [...result.values()];

const userExtensions = await this.webExtensionsScannerService.scanUserExtensions(profileLocation ?? this.userDataProfileService.currentProfile.extensionsResource); extensions.push(...userExtensions); } <del> return Promise.all(extensions.map(e => toLocalExtension(e))); </del> <ins> return extensions.map(e => toLocalExtension(e)); </ins> } async install(location: URI, options: InstallOptions = {}): Promise<ILocalExtension> {