Skip to content

Terminal activation emits source <prefix>/bin/activate for uv-managed toolchains that have no activate script #1775

Description

Environment

Extension ms-python.vscode-python-envs 1.36.0 (also present in 1.37.2026090401, published 2026-09-04)
VS Code 1.137.0
OS macOS 15.6 (arm64)
Shell zsh
Environment manager uv (plus ms-python.python:system for comparison)

Steps to reproduce

  1. uv python install 3.13 — installs a toolchain to ~/.local/share/uv/python/cpython-3.13.9-macos-aarch64-none/.
  2. Open a folder in VS Code whose selected interpreter is that toolchain:
    Ctrl/Cmd+Shift+PPython: Select InterpreterEnter interpreter path…
    ~/.local/share/uv/python/cpython-3.13.9-macos-aarch64-none/bin/python
  3. Open an integrated terminal.

Actual behavior

source /Users/<user>/.local/share/uv/python/cpython-3.13.9-macos-aarch64-none/bin/activate
source: no such file or directory: /Users/<user>/.local/share/uv/python/cpython-3.13.9-macos-aarch64-none/bin/activate

The toolchain's bin/ contains python3.13, python, pip, idle3 — there is no activate.
A uv python install toolchain is a base interpreter, not a virtual environment, so the failure
is not a broken install: the activation command itself should never have been emitted.

Once persisted, the same command is emitted on every new terminal in that folder and
survives restarts — this is not a one-off.

Python Environments log for the same window:

[interpreterSelection] deven367: cpython-3.13.9-macos-aarch64-none (3.13.9) (source: autoDiscovery)
Terminal is activated: /Users/<user>/.local/share/uv/python/cpython-3.13.9-macos-aarch64-none/bin/python

(The extension also auto-discovered and selected this toolchain for a folder with no
environment at all, with no user selection — same outcome.)

Expected behavior

No activation command is emitted for an environment that cannot be activated; either the
toolchain is reported as non-activatable (like system environments, which correctly emit
nothing), or the activation command is gated on the activation script existing.

Cause

getShellActivationCommands(dir) sets the sh/bash/zsh/gitbash (and "unknown") activation
entries unconditionally, while fish/csh/xsh/nu are gated on pathExists:

t.getShellActivationCommands = async function (e) {
  const t = new Map(), n = new Map();
  return isWindows()
    ? t.set("unknown", [{ executable: join(e, "activate") }])
    : t.set("unknown", [{ executable: "source", args: [join(e, "activate")] }]),   // no pathExists
  t.set(SH,      [{ executable: "source", args: [join(e, "activate")] }]),         // no pathExists
  t.set(BASH,    [{ executable: "source", args: [join(e, "activate")] }]),         // no pathExists
  t.set(GITBASH, [{ executable: "source", args: [v(join(e, "activate"))] }]),      // no pathExists
  t.set(ZSH,     [{ executable: "source", args: [join(e, "activate")] }]),         // no pathExists
  ...
  await pathExists(join(e, "activate.csh")) && t.set(CSH, ...);                    // gated
  await pathExists(join(e, "activate.fish")) && t.set(FISH, ...);                  // gated
  await pathExists(join(e, "activate.xsh"))  && t.set(XSH, ...);                   // gated
  await pathExists(join(e, "activate.nu"))   && t.set(NU, ...);                    // gated
  return { shellActivation: t, shellDeactivation: n };
};

It is called with dirname(executable), i.e. <prefix>/bin:

const a = dirname(e.executable),
      { shellActivation: o, shellDeactivation: s } = await getShellActivationCommands(a);

So for any prefix without bin/activate, a failing command is produced instead of an
environment with no activation. isActivatableEnvironment() then returns true, because it
only checks that shellActivation is non-empty:

t.isActivatableEnvironment = function (e) {
  return !!e.execInfo?.activation || !!e.execInfo?.shellActivation;
};

The uv manager registers these toolchains through the venv path — the log shows
Found venv environment: cpython-3.13.9-macos-aarch64-none (3.13.9) for a directory that has
no pyvenv.cfg and no activate. system environments, which carry no shellActivation,
behave correctly and emit nothing.

Suggested fix

  • Gate the sh/bash/zsh/gitbash/unknown entries on pathExists(join(dir, "activate")), matching
    the existing fish/csh/xsh/nu checks, so a prefix without an activation script yields an
    effectively non-activatable environment.
  • Optionally, additionally report uv python install toolchains as non-activatable (they have
    no pyvenv.cfg), so they are not offered as activatable venv environments.

Workaround

Pin the folder to the system manager, which emits no activation command:

"python-envs.pythonProjects": [
  { "path": "<folder>", "envManager": "ms-python.python:system" }
]

or select a real .venv / system interpreter instead of the uv toolchain.

Additional context

Reproduced by selecting the interpreter through Python: Select Interpreter → Enter interpreter
path…
in the GUI, and independently by restoring the persisted
ms-python.vscode-python-envs:venv:WORKSPACE_SELECTED entry the picker writes.

Unrelated observation from the same session, in case it is useful: activating the extension
wrote "python-envs.defaultEnvManager": "ms-python.python:venv" into the workspace
settings.json (~/.vscode/settings.json) by itself.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions