Skip to content

Commit 5ea9d15

Browse files
fix: stop resolve() from leaking foreign envs (e.g. hatch) into the venv collection (#1507)
### Context @flying-sheep reported three related bugs — #1471, #1485, #1491. The two venv-related ones both traced to the same root cause in `VenvManager.resolve()`. Hatch envs structurally look like venvs (they have a `pyvenv.cfg`), so until very recently PET classified them as `Venv` and they passed through `resolveVenvPythonEnvironmentPath`. Every successful `resolve()` call also mutated `this.collection` via `addEnvironment(resolved, true)`. So whenever anything called `resolve()` for a hatch path — `Python: Select Interpreter`, Pylance startup, a test runner asking for execInfo, another extension querying the env — the hatch env got persisted under venv. The original side effect dates back to commit [9363bc1](9363bc1) (Oct 2024), when the extension only had `system` and `venv` managers and the author assumed any successful `resolve()` was a newly discovered venv worth caching. That assumption stopped being true once the API supported third-party managers. ### Upstream fix has now landed in PET As of [microsoft/python-environment-tools#460](microsoft/python-environment-tools#460) (commit [d581272](microsoft/python-environment-tools@d581272)), PET now has a dedicated `pet-hatch` locator: - New `LocatorKind::Hatch` and `PythonEnvironmentKind::Hatch`. - Registered **before** the `Venv` locator, so it claims hatch envs first. - Matches hatch's real storage layout (`<data_dir>/env/virtual/<project_name>/<project_id>/<venv_name>`), honors `HATCH_DATA_DIR`, and reads `[tool.hatch.dirs.env].virtual` from `pyproject.toml` / `hatch.toml`. That fixes the structural source of the hatch-as-venv misclassification. ### What this PR does This PR is a complementary, defense-in-depth correction in `src/managers/builtin/venvManager.ts`: ```diff if (resolved) { if (resolved.envId.managerId === `${PYTHON_EXTENSION_ID}:venv`) { - // This is just like finding a new environment or creating a new one. - // Add it to collection, and trigger the added event. - this.addEnvironment(resolved, true); - // We should only return the resolved env if it is a venv. return resolved; } } return undefined; ``` `resolve()` is now a pure query: *"given a path, are you the manager for it?"* It still returns the resolved env (every consumer of `resolve()` keeps working), it just stops mutating state. Cached venvs continue to land in `this.collection` through the normal discovery and creation paths, which is the only path that should be writing to it. ### Why keep this PR alongside the PET fix Even with PET correctly classifying hatch envs, `VenvManager.resolve()` mutating `this.collection` is an invariant violation worth correcting: - **General cross-manager overlap.** Anything that produces a `pyvenv.cfg`-shaped env (tox, nox, custom bootstrap scripts, future tools) would re-introduce the same class of leak. With `resolve()` as a pure query, no future overlap can silently grow the venv collection. - **Locator-ordering regressions.** If PET's locator priority ever shifts again, this change keeps the venv manager from quietly absorbing whatever lands in its lap.
1 parent 5e524ce commit 5ea9d15

1 file changed

Lines changed: 9 additions & 12 deletions

File tree

src/managers/builtin/venvManager.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -336,14 +336,15 @@ export class VenvManager implements EnvironmentManager {
336336
environment: env,
337337
}));
338338

339-
this.collection = (await findVirtualEnvironments(
340-
hardRefresh,
341-
this.nativeFinder,
342-
this.api,
343-
this.log,
344-
this,
345-
scope ? [scope] : undefined,
346-
)) ?? [];
339+
this.collection =
340+
(await findVirtualEnvironments(
341+
hardRefresh,
342+
this.nativeFinder,
343+
this.api,
344+
this.log,
345+
this,
346+
scope ? [scope] : undefined,
347+
)) ?? [];
347348
await this.loadEnvMap();
348349

349350
const added = this.collection.map((env) => ({ environment: env, kind: EnvironmentChangeKind.add }));
@@ -500,10 +501,6 @@ export class VenvManager implements EnvironmentManager {
500501
);
501502
if (resolved) {
502503
if (resolved.envId.managerId === `${PYTHON_EXTENSION_ID}:venv`) {
503-
// This is just like finding a new environment or creating a new one.
504-
// Add it to collection, and trigger the added event.
505-
this.addEnvironment(resolved, true);
506-
507504
// We should only return the resolved env if it is a venv.
508505
// Fall through an return undefined if it is not a venv
509506
return resolved;

0 commit comments

Comments
 (0)