From 9c33a0390a29f4d28b7a25eed2161edfb6a46e9a Mon Sep 17 00:00:00 2001 From: Sankalp Thakur Date: Fri, 18 Sep 2026 01:28:54 +0000 Subject: [PATCH] gh-157692: Isolate saved PATH state in venv batch scripts --- Lib/test/test_venv.py | 30 +++++++++++++++++++ Lib/venv/scripts/nt/activate.bat | 4 +-- Lib/venv/scripts/nt/deactivate.bat | 6 ++-- ...09-18-01-30-00.gh-issue-157692.BatPath.rst | 4 +++ 4 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-09-18-01-30-00.gh-issue-157692.BatPath.rst diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 2f30d3108021dc..a3343dbb710cd1 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -870,6 +870,36 @@ def test_special_chars_windows(self): self.assertTrue(env_name.encode() in lines[0]) self.assertEndsWith(lines[1], env_name.encode()) + @unittest.skipUnless(os.name == 'nt', 'only relevant on Windows') + def test_activate_bat_preserves_foreign_path(self): + # gh-157692: cmd.exe and POSIX shells must not share saved PATH state. + venv.create(self.env_dir) + activate = self.get_env_file(self.bindir, 'activate.bat') + deactivate = self.get_env_file(self.bindir, 'deactivate.bat') + test_batch = self.get_env_file('test_saved_path.bat') + with open(test_batch, 'w') as f: + f.write('@echo off\n' + 'set "_OLD_VIRTUAL_PATH=/posix/saved/path"\n' + 'set "_OLD_VIRTUAL_PATH_BAT="\n' + 'path\n' + f'call "{activate}"\n' + 'path\n' + f'call "{activate}"\n' + 'path\n' + f'call "{deactivate}"\n' + 'path\n' + 'echo FOREIGN:%_OLD_VIRTUAL_PATH%\n') + out, err = check_output([test_batch], encoding='oem') + paths = [line.partition('=')[2] for line in out.splitlines() + if line.upper().startswith('PATH=')] + self.assertEqual(len(paths), 4, out) + original, active, repeated, restored = paths + self.assertEqual(active, + os.path.join(self.env_dir, self.bindir) + ';' + original) + self.assertEqual(repeated, active) + self.assertEqual(restored, original) + self.assertIn('FOREIGN:/posix/saved/path', out.splitlines()) + @unittest.skipUnless(os.name == 'nt', 'only relevant on Windows') def test_unicode_in_batch_file(self): """ diff --git a/Lib/venv/scripts/nt/activate.bat b/Lib/venv/scripts/nt/activate.bat index 4a3e791abb86bd..27dccebed8cf4f 100644 --- a/Lib/venv/scripts/nt/activate.bat +++ b/Lib/venv/scripts/nt/activate.bat @@ -19,8 +19,8 @@ @if defined PYTHONHOME @set _OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME% @set PYTHONHOME= -@if defined _OLD_VIRTUAL_PATH @set PATH=%_OLD_VIRTUAL_PATH% -@if not defined _OLD_VIRTUAL_PATH @set _OLD_VIRTUAL_PATH=%PATH% +@if defined _OLD_VIRTUAL_PATH_BAT @set PATH=%_OLD_VIRTUAL_PATH_BAT% +@if not defined _OLD_VIRTUAL_PATH_BAT @set _OLD_VIRTUAL_PATH_BAT=%PATH% @set "PATH=%VIRTUAL_ENV%\__VENV_BIN_NAME__;%PATH%" @set "VIRTUAL_ENV_PROMPT=__VENV_PROMPT__" diff --git a/Lib/venv/scripts/nt/deactivate.bat b/Lib/venv/scripts/nt/deactivate.bat index 4a04fb7c0bed44..35489973f063d9 100644 --- a/Lib/venv/scripts/nt/deactivate.bat +++ b/Lib/venv/scripts/nt/deactivate.bat @@ -8,11 +8,11 @@ @set _OLD_VIRTUAL_PYTHONHOME= ) -@if defined _OLD_VIRTUAL_PATH ( - @set "PATH=%_OLD_VIRTUAL_PATH%" +@if defined _OLD_VIRTUAL_PATH_BAT ( + @set "PATH=%_OLD_VIRTUAL_PATH_BAT%" ) -@set _OLD_VIRTUAL_PATH= +@set _OLD_VIRTUAL_PATH_BAT= @set VIRTUAL_ENV= @set VIRTUAL_ENV_PROMPT= diff --git a/Misc/NEWS.d/next/Library/2026-09-18-01-30-00.gh-issue-157692.BatPath.rst b/Misc/NEWS.d/next/Library/2026-09-18-01-30-00.gh-issue-157692.BatPath.rst new file mode 100644 index 00000000000000..bc469df47e0fbb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-18-01-30-00.gh-issue-157692.BatPath.rst @@ -0,0 +1,4 @@ +Use separate saved ``PATH`` state for the Windows batch activation +scripts in :mod:`venv`. This prevents a POSIX shell started from an +activated command prompt from restoring a Windows-format ``PATH`` +when it activates a virtual environment.