From fd0c4a544e64572b88a3593c6458204b9212a2d0 Mon Sep 17 00:00:00 2001 From: Jibin7Jose Date: Sun, 11 Oct 2026 20:01:20 +0530 Subject: [PATCH 1/3] fix: preserve bare defaultInterpreterPath commands --- src/features/interpreterSelection.ts | 4 ++++ src/test/features/interpreterSelection.unit.test.ts | 10 ++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/features/interpreterSelection.ts b/src/features/interpreterSelection.ts index 0bd385f14..c73e310b7 100644 --- a/src/features/interpreterSelection.ts +++ b/src/features/interpreterSelection.ts @@ -566,6 +566,10 @@ async function toAbsoluteInterpreterPath(interpreterPath: string, scope: Uri | u if (onPath) { return onPath; } + // Keep bare commands intact when they are absent from the extension host's PATH. + // The native finder can resolve them using its own environment (for example, the + // integrated terminal's shell environment). + return interpreterPath; } if (!scope) { return interpreterPath; diff --git a/src/test/features/interpreterSelection.unit.test.ts b/src/test/features/interpreterSelection.unit.test.ts index 30b5d5858..fd9f6b6f9 100644 --- a/src/test/features/interpreterSelection.unit.test.ts +++ b/src/test/features/interpreterSelection.unit.test.ts @@ -480,14 +480,16 @@ suite('Interpreter Selection - Priority Chain', () => { }); } - test('should resolve a command name that is not on PATH against the workspace folder', async () => { + test('should pass a command name that is not on PATH to the native finder unchanged', async () => { const workspaceUri = Uri.file(path.resolve('test', 'workspace')); const workspaceFolder = { name: 'workspace', uri: workspaceUri } as WorkspaceFolder; - const expected = path.resolve(workspaceUri.fsPath, 'pyenvs-test-missing-python'); + const command = 'pyenvs-test-missing-python'; + originalPath = process.env.PATH; + process.env.PATH = ''; sandbox.stub(workspaceApis, 'getConfiguration').returns(createMockConfig([]) as WorkspaceConfiguration); sandbox.stub(workspaceApis, 'getWorkspaceFolder').returns(workspaceFolder); sandbox.stub(workspaceApis, 'getWorkspaceFolders').returns([workspaceFolder]); - useDefaultInterpreterPath('pyenvs-test-missing-python'); + useDefaultInterpreterPath(command); mockNativeFinder.resolve.rejects(new Error('not found')); mockVenvManager.get.resolves(undefined); @@ -499,7 +501,7 @@ suite('Interpreter Selection - Priority Chain', () => { mockApi as unknown as PythonEnvironmentApi, ); - assert.ok(mockNativeFinder.resolve.calledOnceWithExactly(expected)); + assert.ok(mockNativeFinder.resolve.calledOnceWithExactly(command)); }); test('should treat the default value "python" as not configured', async () => { From b1f3118e1a5e4c4aa755e3389a73c1039fac05d2 Mon Sep 17 00:00:00 2001 From: Jibin Jose Date: Fri, 30 Oct 2026 15:45:30 +0530 Subject: [PATCH 2/3] fix: preserve bare defaultInterpreterPath commands --- src/features/interpreterSelection.ts | 14 ++++++++++ .../interpreterSelection.unit.test.ts | 27 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/src/features/interpreterSelection.ts b/src/features/interpreterSelection.ts index c73e310b7..3e9e86164 100644 --- a/src/features/interpreterSelection.ts +++ b/src/features/interpreterSelection.ts @@ -569,6 +569,20 @@ async function toAbsoluteInterpreterPath(interpreterPath: string, scope: Uri | u // Keep bare commands intact when they are absent from the extension host's PATH. // The native finder can resolve them using its own environment (for example, the // integrated terminal's shell environment). + if (!scope) { + return interpreterPath; + } + const workspaceFolder = getWorkspaceFolder(scope); + const workspacePath = workspaceFolder?.uri.fsPath ?? scope.fsPath; + const workspaceCandidate = path.resolve(workspacePath, interpreterPath); + try { + if ((await fs.stat(workspaceCandidate)).isDirectory()) { + return workspaceCandidate; + } + } catch { + // A missing bare name may still be resolvable by the native finder (for example, by + // an integrated-terminal environment manager), so pass it through unchanged. + } return interpreterPath; } if (!scope) { diff --git a/src/test/features/interpreterSelection.unit.test.ts b/src/test/features/interpreterSelection.unit.test.ts index fd9f6b6f9..72883eb88 100644 --- a/src/test/features/interpreterSelection.unit.test.ts +++ b/src/test/features/interpreterSelection.unit.test.ts @@ -504,6 +504,33 @@ suite('Interpreter Selection - Priority Chain', () => { assert.ok(mockNativeFinder.resolve.calledOnceWithExactly(command)); }); + test('should resolve a bare workspace directory to its absolute path', async () => { + const workspaceUri = Uri.file(await fs.mkdtemp(path.join(os.tmpdir(), 'dip-workspace-'))); + const workspaceFolder = { name: 'workspace', uri: workspaceUri } as WorkspaceFolder; + const envDirectory = path.join(workspaceUri.fsPath, '.venv'); + await fs.mkdir(envDirectory); + sandbox.stub(workspaceApis, 'getConfiguration').returns(createMockConfig([]) as WorkspaceConfiguration); + sandbox.stub(workspaceApis, 'getWorkspaceFolder').returns(workspaceFolder); + sandbox.stub(workspaceApis, 'getWorkspaceFolders').returns([workspaceFolder]); + useDefaultInterpreterPath('.venv'); + mockNativeFinder.resolve.rejects(new Error('not found')); + mockVenvManager.get.resolves(undefined); + + try { + await resolveEnvironmentByPriority( + workspaceUri, + mockEnvManagers as unknown as EnvironmentManagers, + mockProjectManager as unknown as PythonProjectManager, + mockNativeFinder as unknown as NativePythonFinder, + mockApi as unknown as PythonEnvironmentApi, + ); + + assert.ok(mockNativeFinder.resolve.calledOnceWithExactly(envDirectory)); + } finally { + await fs.remove(workspaceUri.fsPath); + } + }); + test('should treat the default value "python" as not configured', async () => { await putCommandOnPath('python'); sandbox.stub(workspaceApis, 'getConfiguration').returns(createMockConfig([]) as WorkspaceConfiguration); From af8c1eace274f69033a962d3599ee1d8ebef196a Mon Sep 17 00:00:00 2001 From: Jibin Jose Date: Wed, 4 Nov 2026 10:49:21 +0530 Subject: [PATCH 3/3] fix: use resolved executable fo bare interpretr commands --- src/features/interpreterSelection.ts | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/features/interpreterSelection.ts b/src/features/interpreterSelection.ts index 3e9e86164..0b043d4d8 100644 --- a/src/features/interpreterSelection.ts +++ b/src/features/interpreterSelection.ts @@ -561,20 +561,24 @@ async function toAbsoluteInterpreterPath(interpreterPath: string, scope: Uri | u if (path.isAbsolute(interpreterPath)) { return interpreterPath; } - if (!/[\\/]/.test(interpreterPath)) { + const isBareCommand = !/[\\/]/.test(interpreterPath); + if (isBareCommand) { const onPath = await findCommandOnPath(interpreterPath); if (onPath) { return onPath; } + } + if (!scope) { + return interpreterPath; + } + if (isBareCommand) { // Keep bare commands intact when they are absent from the extension host's PATH. // The native finder can resolve them using its own environment (for example, the // integrated terminal's shell environment). - if (!scope) { - return interpreterPath; - } - const workspaceFolder = getWorkspaceFolder(scope); - const workspacePath = workspaceFolder?.uri.fsPath ?? scope.fsPath; - const workspaceCandidate = path.resolve(workspacePath, interpreterPath); + const workspaceCandidate = path.resolve( + getWorkspaceFolder(scope)?.uri.fsPath ?? scope.fsPath, + interpreterPath, + ); try { if ((await fs.stat(workspaceCandidate)).isDirectory()) { return workspaceCandidate; @@ -585,9 +589,6 @@ async function toAbsoluteInterpreterPath(interpreterPath: string, scope: Uri | u } return interpreterPath; } - if (!scope) { - return interpreterPath; - } const workspaceFolder = getWorkspaceFolder(scope); return path.resolve(workspaceFolder?.uri.fsPath ?? scope.fsPath, interpreterPath); } @@ -658,6 +659,7 @@ async function tryResolveInterpreterPath( } if (manager && resolvedEnv) { + const isBareCommand = !/[\\/]/.test(interpreterPath); // Create a wrapper environment that uses the user's specified path const newEnv: PythonEnvironment = { envId: { @@ -668,11 +670,11 @@ async function tryResolveInterpreterPath( displayName: 'defaultInterpreterPath: ' + (resolved.version ?? ''), version: resolved.version ?? '', displayPath: interpreterPath, - environmentPath: Uri.file(interpreterPath), + environmentPath: isBareCommand ? resolvedEnv.environmentPath : Uri.file(interpreterPath), sysPrefix: resolved.prefix ?? '', execInfo: { run: { - executable: interpreterPath, + executable: isBareCommand ? resolved.executable : interpreterPath, }, }, };