Skip to content

Report an empty version for conda environments without Python - #1716

Open
Han (LH-and-FPGA) wants to merge 1 commit into
microsoft:mainfrom
LH-and-FPGA:conda-fix
Open

Report an empty version for conda environments without Python#1716
Han (LH-and-FPGA) wants to merge 1 commit into
microsoft:mainfrom
LH-and-FPGA:conda-fix

Conversation

@LH-and-FPGA

Copy link
Copy Markdown

Summary

A conda environment that has no Python interpreter makes all conda environments
disappear from the interpreter picker and the Jupyter kernel picker.

This happens with a prefix created as a toolchain rather than a Python environment,
e.g. conda create -n cuda cuda-toolkit. Reproduced on Linux with 5 conda
environments, one of them interpreter-less; only base survived.

Likely the same root cause as #1584 — that report's environment list shows a third
entry rendered as (no-python), and it was closed as info-needed.

Root cause

getCondaWithoutPython describes an interpreter-less environment with a placeholder
in a field that consumers parse:

// src/managers/conda/condaUtils.ts
version: 'no-python',

version is declared readonly version: string in src/api.ts, so a sentinel
compiles fine — but ms-python.python parses it as a PEP 440 version:

// ms-python.python 2026.7.2026080801, out/client/extension.js
function f(e){ const t = l.parseBasicVersionInfo(`ignored-${e}`);
  if(!t){ if("" === e) return [v(), ""];        // "unknown" — degrades gracefully
          throw Error(`invalid version ${e}`) } }  // 'no-python' lands here

The empty string is the one unparseable value it accepts. Any other placeholder
throws.

Why one bad environment takes down the rest. This repository is careful —
condaUtils.ts wraps each environment in its own try/catch during conversion. But
the sentinel is valid data, so it passes that guard and is published in the batch
fired from condaEnvManager.ts. The consumer has no per-item guard:

onDidChangeEnvironments(e){ e.forEach(e => {  this.addEnv(e.environment) }) }

Array.forEach cannot resume after a throw, so every environment ordered after the
offending one is silently dropped. Observed stack:

at f                              ← throw Error(`invalid version no-python`)
at _                              ← convert PythonEnvironmentInfo
at S.addEnv
at Array.forEach (<anonymous>)    ← no try/catch
at S.onDidChangeEnvironments
at T.fire

The fix

1. Don't publish a sentinel as a version (condaUtils.ts, condaEnvManager.ts)

version: 'no-python'version: ''. This is already how the rest of the repository
represents an unknown version:

  • src/features/interpreterSelection.tsversion: resolved.version ?? ''
  • src/common/inlineScript/interpreter.tsenv.version.length === 0 means not usable

Conda was the only one of the eight managers surfacing a sentinel. The (no-python)
marker stays in displayName / shortDisplayName, which are displayed but never
parsed, so the UI is unchanged. The two internal version === 'no-python' checks now
go through an exported isCondaEnvWithoutPython predicate.

This also fixes a smaller bug: pickPythonVersion builds its list with
.map(e => e.version).filter(Boolean), and 'no-python' is truthy, so "Select the
version of Python to install" offered no-python as a choice.

2. Make sortEnvironments a total order (managers/common/utils.ts)

return a.version ? 1 : -1;

Comparing a real version against an unparseable non-empty one returns 1 in both
directions, which breaks the antisymmetry Array.prototype.sort requires, so the
result is an implementation-defined permutation. Now: valid versions compare
descending, a known version sorts before an unknown one, and two unknowns fall back to
a string comparison.

getLatest had a related gap — seeded with candidates[0], both operands had to parse
for the seed to ever be replaced, so an unparseable seed always won.

Verification

Measured against the real @renovatebot/pep440, on the reported environment set
(base 3.13.13, an interpreter-less prefix, git 3.14.6, lh 3.14.7, vllm 3.14.6):

before after
antisymmetry violations over a 9-version vocabulary 36 0
distinct sort results over 120 input permutations 16 1
getLatest result the interpreter-less env lh
environments published before the consumer throws base only all 5

Tests

  • npm run unittest: 1582 passing, 0 failing (1577 before this change).
  • New utils.sortEnvironments.unit.test.ts — descending order, unknown versions last,
    and stability across all input permutations.
  • New condaUtils.noPythonEnv.unit.test.ts — an interpreter-less prefix is still
    discovered, reports version: '', and keeps its (no-python) display name.
  • Three of the four new tests fail on main and pass with this change; the fourth is a
    baseline that passes on both.
  • Updated the three existing fixtures that constructed environments with 'no-python'.

src/api.ts is unchanged, so no API version bump or changelog entry is needed.

`getCondaWithoutPython` described an interpreter-less conda prefix with
`version: 'no-python'`. `version` is public API and consumers parse it as a
PEP 440 version: `ms-python.python` throws `invalid version no-python` and
consumes the change event with a bare `Array.forEach`, so every environment
ordered after the offending one is dropped — one toolchain-only prefix hides
all conda environments from the interpreter and Jupyter kernel pickers.

Use `''` instead, which is what the rest of the repository already means by
"unknown version" and the only unparseable value `parseVersion` degrades on
rather than throwing. The `(no-python)` marker stays in the display strings,
which are shown but never parsed.

Also make `sortEnvironments` a total order: the `a.version ? 1 : -1` fallback
returned 1 in both directions when comparing a real version against an
unparseable one, leaving the sorted order implementation-defined. `getLatest`
could never replace a seed whose own version did not parse.
@LH-and-FPGA

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant