Fix PYTHONHOME inheritance when spawning different Python versions (#17821)

## Summary

Fixes #17814.

When a Python version installed by uv spawns another Python version via
subprocess (e.g., Python 3.12 spawning Python 3.13), the child process
would inherit the parent's `PYTHONHOME` environment variable. This
caused the child Python to load the wrong stdlib, resulting in errors
like `AssertionError: SRE module mismatch`.

The fix removes the check that prevented overwriting an inherited
`PYTHONHOME` value. Now the Windows trampoline always sets `PYTHONHOME`
to the correct directory for non-virtualenv Python installations,
ensuring each Python version uses its own stdlib.

## Test Plan

Tested manually using the reproduction script from the issue:

```
uv python install 3.12
uv python install 3.13

echo "from os import environ;print(environ['PYTHONHOME']);import re" > test.py
python3.12.exe -c "import subprocess; subprocess.run(['python3.13.exe', 'test.py'])"
```

Before: `AssertionError: SRE module mismatch`
After: Runs successfully with correct `PYTHONHOME` pointing to 3.13's
installation.

---------

Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
Mohammad Miadh Angkad
2026-02-04 01:12:25 +08:00
committed by GitHub
parent f1e4ecf697
commit 396ff569ca
8 changed files with 34 additions and 13 deletions
+6
View File
@@ -568,6 +568,12 @@ impl EnvVars {
#[attr_added_in("0.9.15")]
pub const UV_INTERNAL__TEST_LFS_DISABLED: &'static str = "UV_INTERNAL__TEST_LFS_DISABLED";
/// Marker variable to track whether `PYTHONHOME` was set by uv.
/// Used by the Windows trampoline to distinguish uv-set values from user-set values.
#[attr_hidden]
#[attr_added_in("next release")]
pub const UV_INTERNAL__PYTHONHOME: &'static str = "UV_INTERNAL__PYTHONHOME";
/// Path to system-level configuration directory on Unix systems.
#[attr_added_in("0.4.26")]
pub const XDG_CONFIG_DIRS: &'static str = "XDG_CONFIG_DIRS";
+28 -13
View File
@@ -146,20 +146,35 @@ fn make_child_cmdline() -> CString {
// be correctly detected when using trampolines.
std::env::set_var(EnvVars::PYVENV_LAUNCHER, &executable_name);
// If this is not a virtual environment and `PYTHONHOME` has
// not been set, then set `PYTHONHOME` to the parent directory of
// the executable. This ensures that the correct installation
// directories are added to `sys.path` when running with a junction
// trampoline.
let python_home_set =
std::env::var(EnvVars::PYTHONHOME).is_ok_and(|home| !home.is_empty());
if !is_virtualenv(python_exe.as_path()) && !python_home_set {
std::env::set_var(
EnvVars::PYTHONHOME,
python_exe
// If this is not a virtual environment, set `PYTHONHOME` to
// the parent directory of the executable. This ensures that
// the correct installation directories are added to `sys.path`
// when running with a junction trampoline.
//
// We use a marker variable (`UV_INTERNAL__PYTHONHOME`) to track
// whether `PYTHONHOME` was set by uv. This allows us to:
// - Override inherited `PYTHONHOME` from parent Python processes
// - Preserve user-defined `PYTHONHOME` values
if !is_virtualenv(python_exe.as_path()) {
let python_home = std::env::var(EnvVars::PYTHONHOME).ok();
let marker = std::env::var(EnvVars::UV_INTERNAL__PYTHONHOME).ok();
// Only set `PYTHONHOME` if:
// - It's not set, OR
// - It was set by uv (marker matches current `PYTHONHOME`)
let should_override = match (&python_home, &marker) {
(None, _) => true,
(Some(home), Some(m)) if home == m => true,
_ => false,
};
if should_override {
let home = python_exe
.parent()
.expect("Python executable should have a parent directory"),
);
.expect("Python executable should have a parent directory");
std::env::set_var(EnvVars::PYTHONHOME, home);
std::env::set_var(EnvVars::UV_INTERNAL__PYTHONHOME, home);
}
}
}
}