diff --git a/src/vs/workbench/services/runtimeStartup/common/runtimeStartup.ts b/src/vs/workbench/services/runtimeStartup/common/runtimeStartup.ts index 69be4aee20d..d8a96ae42d9 100644 --- a/src/vs/workbench/services/runtimeStartup/common/runtimeStartup.ts +++ b/src/vs/workbench/services/runtimeStartup/common/runtimeStartup.ts @@ -1616,8 +1616,15 @@ export class RuntimeStartupService extends Disposable implements IRuntimeStartup if (metadata.runtimeId === affiliatedRuntimeId) { // Heal any stale fields in the stored affiliation (e.g. a runtimePath // that was ~-shortened before the runtimePath/runtimeDisplayPath split) - // by overwriting with the freshly-discovered metadata. - this.saveAffiliatedRuntime({ ...affiliated, metadata }); + // by overwriting with the freshly-discovered metadata. Keep the stored + // extraRuntimeData: the extension reads it when validating the + // affiliation, and it records facts from when the runtime was chosen + // (e.g. R's `current` flag, which makes the affiliation follow the + // current R instead of this binary). + this.saveAffiliatedRuntime({ + ...affiliated, + metadata: { ...metadata, extraRuntimeData: affiliated.metadata.extraRuntimeData }, + }); try { diff --git a/src/vs/workbench/services/runtimeStartup/test/common/runtimeStartup.vitest.ts b/src/vs/workbench/services/runtimeStartup/test/common/runtimeStartup.vitest.ts index a4b58c26d29..bd6536234af 100644 --- a/src/vs/workbench/services/runtimeStartup/test/common/runtimeStartup.vitest.ts +++ b/src/vs/workbench/services/runtimeStartup/test/common/runtimeStartup.vitest.ts @@ -1238,6 +1238,43 @@ describe('RuntimeStartupService - affiliation healing', () => { // getAffiliatedRuntimes() returns the live registered metadata (absolute path). expect(svc.getAffiliatedRuntimes()[0].runtimePath).toBe(freshRuntimePath); }); + + it('keeps a stored affiliation that follows the current R when an older R re-registers', async () => { + // R runtime IDs hash only the binary path and version, so the same + // runtime ID comes back after `rig default` points at a newer R. The + // R extension's validateMetadata only swaps in the current R when the + // stored affiliation says `current: true`. + const runtimeId = 'r-4.5-current-flag-test'; + const languageId = 'r'; + const runtimePath = '/Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/bin/R'; + const storageKey = `positron.affiliatedRuntimeMetadata.v2.${languageId}`; + const scope = StorageScope.WORKSPACE; + + // Affiliated while R 4.5 was the current R. + const affiliated = { + metadata: { + ...metadata({ languageId, runtimePath, runtimeId, extensionId: 'ms.r' }), + extraRuntimeData: { binpath: runtimePath, current: true }, + }, + lastUsed: 0, + lastStarted: 0, + }; + ctx.get(IStorageService).store(storageKey, JSON.stringify(affiliated), scope, StorageTarget.MACHINE); + + const svc = ctx.disposables.add( + ctx.instantiationService.createInstance(RuntimeStartupService)) as RuntimeStartupService; + (svc as unknown as { _startupPhase: RuntimeStartupPhase })._startupPhase = RuntimeStartupPhase.LoadingCache; + + // Re-discovered after a newer R became current. + await ctx.get(ILanguageRuntimeService).registerRuntime({ + ...metadata({ languageId, runtimePath, runtimeId, extensionId: 'ms.r' }), + startupBehavior: LanguageRuntimeStartupBehavior.Manual, + extraRuntimeData: { binpath: runtimePath, current: false }, + }); + + const stored = JSON.parse(ctx.get(IStorageService).get(storageKey, scope)!); + expect(stored.metadata.extraRuntimeData.current).toBe(true); + }); }); describe('RuntimeStartupService - restored sessions', () => {