From aa0ad0b898900af45b1ec4bd68ff6d73b94d1ffe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 22:01:54 +0000 Subject: [PATCH 1/5] Initial plan From 7bb04d427e025a2d6c2ba22ce5f83dc62eaf08f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Apr 2026 22:05:52 +0000 Subject: [PATCH 2/5] Fix: Make Set-ExecutionPolicy Windows-only in PowerShell activation (Fixes #1399) Agent-Logs-Url: https://github.com/microsoft/vscode-python-environments/sessions/d5ab0bb9-7aa5-4168-a807-dd9bbb72dace Co-authored-by: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> --- src/managers/common/utils.ts | 28 +++++++-------- ...ls.getShellActivationCommands.unit.test.ts | 36 +++++++++++++++++++ 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/managers/common/utils.ts b/src/managers/common/utils.ts index 828f6e10..0d084da5 100644 --- a/src/managers/common/utils.ts +++ b/src/managers/common/utils.ts @@ -139,6 +139,18 @@ export function compareVersions(version1: string, version2: string): number { return 0; } +function buildPwshActivationCommands(ps1Path: string): PythonCommandRunConfiguration[] { + const commands: PythonCommandRunConfiguration[] = []; + if (isWindows()) { + commands.push({ + executable: 'Set-ExecutionPolicy', + args: ['-Scope', 'Process', '-ExecutionPolicy', 'RemoteSigned'], + }); + } + commands.push({ executable: '&', args: [ps1Path] }); + return commands; +} + export async function getShellActivationCommands(binDir: string): Promise<{ shellActivation: Map; shellDeactivation: Map; @@ -172,22 +184,10 @@ export async function getShellActivationCommands(binDir: string): Promise<{ shellDeactivation.set(ShellConstants.KSH, [{ executable: 'deactivate' }]); if (await fs.pathExists(path.join(binDir, 'Activate.ps1'))) { - shellActivation.set(ShellConstants.PWSH, [ - { - executable: 'Set-ExecutionPolicy', - args: ['-Scope', 'Process', '-ExecutionPolicy', 'RemoteSigned'], - }, - { executable: '&', args: [path.join(binDir, `Activate.ps1`)] }, - ]); + shellActivation.set(ShellConstants.PWSH, buildPwshActivationCommands(path.join(binDir, 'Activate.ps1'))); shellDeactivation.set(ShellConstants.PWSH, [{ executable: 'deactivate' }]); } else if (await fs.pathExists(path.join(binDir, 'activate.ps1'))) { - shellActivation.set(ShellConstants.PWSH, [ - { - executable: 'Set-ExecutionPolicy', - args: ['-Scope', 'Process', '-ExecutionPolicy', 'RemoteSigned'], - }, - { executable: '&', args: [path.join(binDir, `activate.ps1`)] }, - ]); + shellActivation.set(ShellConstants.PWSH, buildPwshActivationCommands(path.join(binDir, 'activate.ps1'))); shellDeactivation.set(ShellConstants.PWSH, [{ executable: 'deactivate' }]); } diff --git a/src/test/managers/common/utils.getShellActivationCommands.unit.test.ts b/src/test/managers/common/utils.getShellActivationCommands.unit.test.ts index 350cd013..756d30e0 100644 --- a/src/test/managers/common/utils.getShellActivationCommands.unit.test.ts +++ b/src/test/managers/common/utils.getShellActivationCommands.unit.test.ts @@ -80,6 +80,42 @@ suite('getShellActivationCommands', () => { }); }); + suite('PowerShell activation on non-Windows omits Set-ExecutionPolicy', () => { + test('Activate.ps1 (capitalized) on non-Windows has only the activation command', async () => { + isWindowsStub.returns(false); + await fs.writeFile(path.join(tmpDir, 'Activate.ps1'), ''); + + const result = await getShellActivationCommands(tmpDir); + const pwshActivation = result.shellActivation.get(ShellConstants.PWSH); + + assert.ok(pwshActivation, 'PowerShell activation should be defined'); + assert.strictEqual(pwshActivation.length, 1, 'Should have only 1 command: activate (no Set-ExecutionPolicy)'); + assert.strictEqual(pwshActivation[0].executable, '&'); + assert.ok(pwshActivation[0].args); + assert.ok( + pwshActivation[0].args[0].endsWith('Activate.ps1'), + `Expected path ending with Activate.ps1, got: ${pwshActivation[0].args[0]}`, + ); + }); + + test('activate.ps1 (lowercase) on non-Windows has only the activation command', async () => { + isWindowsStub.returns(false); + await fs.writeFile(path.join(tmpDir, 'activate.ps1'), ''); + + const result = await getShellActivationCommands(tmpDir); + const pwshActivation = result.shellActivation.get(ShellConstants.PWSH); + + assert.ok(pwshActivation, 'PowerShell activation should be defined'); + assert.strictEqual(pwshActivation.length, 1, 'Should have only 1 command: activate (no Set-ExecutionPolicy)'); + assert.strictEqual(pwshActivation[0].executable, '&'); + assert.ok(pwshActivation[0].args); + assert.ok( + pwshActivation[0].args[0].endsWith('activate.ps1'), + `Expected path ending with activate.ps1, got: ${pwshActivation[0].args[0]}`, + ); + }); + }); + suite('No PowerShell activation when Activate.ps1 is absent', () => { test('No pwsh activation when no ps1 file exists', async () => { isWindowsStub.returns(true); From 5ba482e4a7bc64212c02f5ab582fcec7c670dd37 Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Tue, 28 Apr 2026 20:29:05 -0700 Subject: [PATCH 3/5] Fix ps1 path casing on case-insensitive filesystems (Windows) Use fs.readdir to find the actual activate.ps1 filename with correct casing instead of checking hardcoded 'Activate.ps1'/'activate.ps1'. On Windows, pathExists is case-insensitive so the wrong cased path would be returned when the file has different casing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/managers/common/utils.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/managers/common/utils.ts b/src/managers/common/utils.ts index 0d084da5..89814cbd 100644 --- a/src/managers/common/utils.ts +++ b/src/managers/common/utils.ts @@ -183,11 +183,14 @@ export async function getShellActivationCommands(binDir: string): Promise<{ shellActivation.set(ShellConstants.KSH, [{ executable: '.', args: [path.join(binDir, `activate`)] }]); shellDeactivation.set(ShellConstants.KSH, [{ executable: 'deactivate' }]); - if (await fs.pathExists(path.join(binDir, 'Activate.ps1'))) { - shellActivation.set(ShellConstants.PWSH, buildPwshActivationCommands(path.join(binDir, 'Activate.ps1'))); - shellDeactivation.set(ShellConstants.PWSH, [{ executable: 'deactivate' }]); - } else if (await fs.pathExists(path.join(binDir, 'activate.ps1'))) { - shellActivation.set(ShellConstants.PWSH, buildPwshActivationCommands(path.join(binDir, 'activate.ps1'))); + // Use readdir to find the actual ps1 filename with correct casing. + // pathExists with a hardcoded name fails on case-insensitive filesystems (Windows) + // where 'activate.ps1' created by the user would match 'Activate.ps1' in the check, + // causing the returned path to have the wrong case. + const dirFiles = await fs.readdir(binDir).catch(() => [] as string[]); + const ps1File = dirFiles.find((f) => f.toLowerCase() === 'activate.ps1'); + if (ps1File) { + shellActivation.set(ShellConstants.PWSH, buildPwshActivationCommands(path.join(binDir, ps1File))); shellDeactivation.set(ShellConstants.PWSH, [{ executable: 'deactivate' }]); } From 43e818068214c65d76e11dc132903d6aec82e677 Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Tue, 28 Apr 2026 20:31:46 -0700 Subject: [PATCH 4/5] Revert "Fix ps1 path casing on case-insensitive filesystems (Windows)" This reverts commit 5ba482e4a7bc64212c02f5ab582fcec7c670dd37. --- src/managers/common/utils.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/managers/common/utils.ts b/src/managers/common/utils.ts index 89814cbd..0d084da5 100644 --- a/src/managers/common/utils.ts +++ b/src/managers/common/utils.ts @@ -183,14 +183,11 @@ export async function getShellActivationCommands(binDir: string): Promise<{ shellActivation.set(ShellConstants.KSH, [{ executable: '.', args: [path.join(binDir, `activate`)] }]); shellDeactivation.set(ShellConstants.KSH, [{ executable: 'deactivate' }]); - // Use readdir to find the actual ps1 filename with correct casing. - // pathExists with a hardcoded name fails on case-insensitive filesystems (Windows) - // where 'activate.ps1' created by the user would match 'Activate.ps1' in the check, - // causing the returned path to have the wrong case. - const dirFiles = await fs.readdir(binDir).catch(() => [] as string[]); - const ps1File = dirFiles.find((f) => f.toLowerCase() === 'activate.ps1'); - if (ps1File) { - shellActivation.set(ShellConstants.PWSH, buildPwshActivationCommands(path.join(binDir, ps1File))); + if (await fs.pathExists(path.join(binDir, 'Activate.ps1'))) { + shellActivation.set(ShellConstants.PWSH, buildPwshActivationCommands(path.join(binDir, 'Activate.ps1'))); + shellDeactivation.set(ShellConstants.PWSH, [{ executable: 'deactivate' }]); + } else if (await fs.pathExists(path.join(binDir, 'activate.ps1'))) { + shellActivation.set(ShellConstants.PWSH, buildPwshActivationCommands(path.join(binDir, 'activate.ps1'))); shellDeactivation.set(ShellConstants.PWSH, [{ executable: 'deactivate' }]); } From 437077310331586eae815916ab05597bbfc805d0 Mon Sep 17 00:00:00 2001 From: eleanorjboyd <26030610+eleanorjboyd@users.noreply.github.com> Date: Tue, 28 Apr 2026 20:32:01 -0700 Subject: [PATCH 5/5] Fix test: use case-insensitive path check for activate.ps1 on Windows On Windows, fs.pathExists is case-insensitive so the code may return 'Activate.ps1' even when only 'activate.ps1' exists on disk. The test assertion should not depend on exact casing of the returned path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../common/utils.getShellActivationCommands.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/managers/common/utils.getShellActivationCommands.unit.test.ts b/src/test/managers/common/utils.getShellActivationCommands.unit.test.ts index 756d30e0..9f21bf61 100644 --- a/src/test/managers/common/utils.getShellActivationCommands.unit.test.ts +++ b/src/test/managers/common/utils.getShellActivationCommands.unit.test.ts @@ -110,7 +110,7 @@ suite('getShellActivationCommands', () => { assert.strictEqual(pwshActivation[0].executable, '&'); assert.ok(pwshActivation[0].args); assert.ok( - pwshActivation[0].args[0].endsWith('activate.ps1'), + pwshActivation[0].args[0].toLowerCase().endsWith('activate.ps1'), `Expected path ending with activate.ps1, got: ${pwshActivation[0].args[0]}`, ); });