Skip to content

🐛 fix(discovery): resolve version-manager shims to real binaries#3067

Merged
gaborbernat merged 6 commits into
pypa:mainfrom
gaborbernat:other
Feb 23, 2026
Merged

🐛 fix(discovery): resolve version-manager shims to real binaries#3067
gaborbernat merged 6 commits into
pypa:mainfrom
gaborbernat:other

Conversation

@gaborbernat

Copy link
Copy Markdown
Contributor

Version managers like pyenv, mise, and asdf place shim scripts on PATH that delegate to the real Python binary. When virtualenv interrogates these shims as subprocesses during discovery, they can resolve to the system Python instead of the version-manager-managed binary. This happens because the shim's delegation mechanism may behave differently in a subprocess context than in an interactive shell — resulting in users getting the wrong Python version in their virtualenv. 🐛

The fix bypasses shims entirely during PATH discovery by reading .python-version files (a convention shared across pyenv, mise, and asdf) and locating the real binary directly under the version manager's install directory. Resolution follows the same priority order these tools use natively: PYENV_VERSION env var, then .python-version file searching parent directories from cwd, then the global version file. The version-manager layouts are defined in a data-driven table (PYENV_ROOT/versions/, MISE_DATA_DIR/installs/python/, ASDF_DATA_DIR/installs/python/) making it straightforward to extend for other managers. No subprocess calls to version-manager CLIs are made — only file reads and env var checks.

When no version-manager environment is detected (no PYENV_ROOT, MISE_DATA_DIR, or ASDF_DATA_DIR set), the resolution is skipped entirely with no overhead. If the shim can't be resolved (version not installed, binary missing), discovery falls through to the normal subprocess interrogation path.

Closes #3049

When pyenv/mise/asdf shims are found on PATH and interrogated as
subprocesses, they may resolve to the system Python instead of the
version-manager-managed binary. This happens because the shim's
delegation mechanism can behave differently in subprocess contexts
than in an interactive shell.

Bypass shims entirely by reading .python-version files (a convention
shared across pyenv, mise, and asdf) and locating the real binary
directly under the version manager's install directory. The resolution
checks PYENV_VERSION env var, .python-version files searching up from
cwd, and the global version file, matching the same priority order
these tools use natively.

Closes pypa#3049
@gaborbernat gaborbernat enabled auto-merge (squash) February 23, 2026 15:52
gaborbernat and others added 3 commits February 23, 2026 15:58
Cover shim resolution across all four Diataxis dimensions: explain the
problem and resolution mechanism, add a how-to for pyenv/mise/asdf
users, list version managers in compatibility reference, and mention
them in the tutorial prerequisites. Add a changelog fragment for the
feature.
Shim tests using python3.x specs failed on CI runners where the system
Python matched the spec, causing get_interpreter to return early via
PythonInfo.current_system() before reaching PATH/shim scanning.

Using python2.7 as the spec avoids this entirely since no CI runner has
Python 2.7 installed, so discovery always falls through to PATH scanning
where the shim resolution logic is exercised. This replaces the previous
approach of mocking PythonInfo.current_system() which was fragile and
didn't replicate real version manager behavior.
@gaborbernat gaborbernat merged commit 4582e41 into pypa:main Feb 23, 2026
56 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python discovery resolves pyenv shims to system Python instead of pyenv-managed version

1 participant