Skip to content

Commit dd372a8

Browse files
Avoid selecting no-Python Conda base on startup (#1707)
## Context Fixes [#1689](#1689). The reporter uses Conda/Micromamba with: - `python-envs.defaultEnvManager` set to Conda - a valid named environment (`c8`) configured through `python.defaultInterpreterPath` - a Micromamba `base` prefix that legitimately does not contain Python On startup, the workspace-specific environment resolves successfully to `c8`, but global selection is resolved separately (and may be deferred). The issue log shows that the global path selected the no-Python base through `defaultEnvManager`: ```text Changed environment from undefined to base (no-python) for: global [interpreterSelection] global: base (no-python) (source: defaultEnvManager) ``` Passing that automatically selected environment to `CondaEnvManager.set()` invokes the same no-Python check used for explicit user selection, producing the modal `No python found in the selected conda environment: base`. This can vary by workspace because Conda's global selection is stored in VS Code workspace state. A workspace with a previously persisted usable selection does not take the implicit base fallback, while a fresh workspace commonly does. ## Root cause When no persisted global Conda selection exists, `CondaEnvManager.loadEnvMap()` unconditionally uses the environment named `base` as its implicit global fallback. A no-Python Conda prefix is valid for discovery and display in the Environment Manager, but it is not a usable Python interpreter and should not be selected automatically. The explicit-selection flow is different: when a user intentionally selects a no-Python Conda environment, the existing modal offers to install Python into it. That behavior should remain available. ## Fix Only assign `base` as the implicit global fallback when it contains Python: ```ts const base = this.findEnvironmentByName('base'); if (base?.version !== 'no-python') { this.globalEnv = base; } ``` With an empty Micromamba base and no persisted global selection, `globalEnv` remains undefined. Initial selection therefore never passes the no-Python base into the modal-producing `set()` path. ## Behavior preserved - A valid base environment containing Python remains the implicit global fallback. - Explicitly persisted no-Python selections are restored, preserving user intent. - Explicitly selecting a no-Python environment still presents the install-Python modal. - No-Python environments remain in the discovered collection and visible in the Environment Manager. - Workspace-specific Conda selections, named/prefix environments, discovery, settings, and persistence formats are unchanged. - Interpreter priority resolution is unchanged; this PR intentionally prevents the invalid implicit selection rather than changing `defaultEnvManager` precedence. If no usable persisted global selection exists and base has no Python, the effective global environment is now undefined instead of an unusable interpreter. ## Testing Added coverage for all relevant initialization boundaries: - no-Python base is not used as the implicit global fallback - base containing Python remains the implicit global fallback - explicitly persisted no-Python base remains selected Validation performed: - `npm run compile-tests` - focused Conda initialization, global-selection, and interpreter-selection unit suites (57 passing)
1 parent dbb10ec commit dd372a8

2 files changed

Lines changed: 45 additions & 2 deletions

File tree

‎src/managers/conda/condaEnvManager.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,10 @@ export class CondaEnvManager implements EnvironmentManager, Disposable {
509509

510510
// If a global environment is still not set, try using the 'base'
511511
if (!this.globalEnv) {
512-
this.globalEnv = this.findEnvironmentByName('base');
512+
const base = this.findEnvironmentByName('base');
513+
if (base?.version !== 'no-python') {
514+
this.globalEnv = base;
515+
}
513516
}
514517

515518
// Find any conda environments that might be associated with the current projects

‎src/test/managers/conda/condaEnvManager.initialize.unit.test.ts‎

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import assert from 'assert';
33
import * as sinon from 'sinon';
4+
import { Uri } from 'vscode';
45
import { PythonEnvironmentApi } from '../../../api';
56
import * as logging from '../../../common/logging';
67
import { EventNames } from '../../../common/telemetry/constants';
@@ -27,6 +28,7 @@ import { makeMockCondaEnvironment as makeEnv } from '../../mocks/pythonEnvironme
2728
suite('CondaEnvManager.initialize - lazy registration flow', () => {
2829
let getCondaStub: sinon.SinonStub;
2930
let getCondaPathSettingStub: sinon.SinonStub;
31+
let getCondaForGlobalStub: sinon.SinonStub;
3032
let refreshCondaEnvsStub: sinon.SinonStub;
3133
let constructSourcingStub: sinon.SinonStub;
3234
let notifyMissingStub: sinon.SinonStub;
@@ -37,7 +39,7 @@ suite('CondaEnvManager.initialize - lazy registration flow', () => {
3739
getCondaStub = sinon.stub(condaUtils, 'getConda');
3840
getCondaPathSettingStub = sinon.stub(condaUtils, 'getCondaPathSetting').returns(undefined);
3941
refreshCondaEnvsStub = sinon.stub(condaUtils, 'refreshCondaEnvs').resolves([]);
40-
sinon.stub(condaUtils, 'getCondaForGlobal').resolves(undefined);
42+
getCondaForGlobalStub = sinon.stub(condaUtils, 'getCondaForGlobal').resolves(undefined);
4143
constructSourcingStub = sinon.stub(condaSourcingUtils, 'constructCondaSourcingStatus');
4244
notifyMissingStub = sinon.stub(commonUtils, 'notifyMissingManagerIfDefault').resolves();
4345
sendTelemetryStub = sinon.stub(telemetrySender, 'sendTelemetryEvent');
@@ -101,6 +103,44 @@ suite('CondaEnvManager.initialize - lazy registration flow', () => {
101103
);
102104
});
103105

106+
test('does not use a no-Python base as the implicit global fallback', async () => {
107+
getCondaStub.resolves('/usr/bin/conda');
108+
constructSourcingStub.resolves({ toString: () => '' } as any);
109+
const base = makeEnv('base', Uri.file('/opt/miniconda3').fsPath, 'no-python');
110+
refreshCondaEnvsStub.resolves([base]);
111+
112+
const mgr = createManager();
113+
await mgr.initialize();
114+
115+
assert.strictEqual(await mgr.get(undefined), undefined);
116+
});
117+
118+
test('uses a base with Python as the implicit global fallback', async () => {
119+
getCondaStub.resolves('/usr/bin/conda');
120+
constructSourcingStub.resolves({ toString: () => '' } as any);
121+
const base = makeEnv('base', Uri.file('/opt/miniconda3').fsPath, '3.12.0');
122+
refreshCondaEnvsStub.resolves([base]);
123+
124+
const mgr = createManager();
125+
await mgr.initialize();
126+
127+
assert.strictEqual(await mgr.get(undefined), base);
128+
});
129+
130+
test('retains a persisted no-Python base selection', async () => {
131+
getCondaStub.resolves('/usr/bin/conda');
132+
constructSourcingStub.resolves({ toString: () => '' } as any);
133+
const basePath = Uri.file('/opt/miniconda3').fsPath;
134+
const base = makeEnv('base', basePath, 'no-python');
135+
refreshCondaEnvsStub.resolves([base]);
136+
getCondaForGlobalStub.resolves(basePath);
137+
138+
const mgr = createManager();
139+
await mgr.initialize();
140+
141+
assert.strictEqual(await mgr.get(undefined), base);
142+
});
143+
104144
test('success path: conda from explicit setting → toolSource=settings', async () => {
105145
getCondaPathSettingStub.returns('/opt/conda/bin/conda');
106146
getCondaStub.resolves('/opt/conda/bin/conda');

0 commit comments

Comments
 (0)