diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 68bcf535eada10..fd71640a04f193 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -597,6 +597,25 @@ def test_failed_symlink(self): filepath_regex = r"'[A-Z]:\\\\(?:[^\\\\]+\\\\)*[^\\\\]+'" self.assertRegex(err, rf"Unable to symlink {filepath_regex} to {filepath_regex}") + @requireVenvCreate + @unittest.skipIf(os.name == 'nt', 'not relevant on Windows') + @unittest.skipUnless(can_symlink(), 'Needs symlinks') + def test_broken_symlink_in_existing_venv(self): + """ + Test creating a venv when a stale venv with broken symlinks exists. + """ + bindir = os.path.join(self.env_dir, self.bindir) + os.makedirs(bindir) + python = os.path.join(bindir, 'python3') + os.symlink('/path/to/deleted/conda/env/bin/python3', python) + self.assertTrue(os.path.islink(python)) + self.assertFalse(os.path.exists(python)) + + builder = venv.EnvBuilder(with_pip=False, symlinks=True) + self.run_with_capture(builder.create, self.env_dir) + self.assertTrue(os.path.islink(python)) + self.assertTrue(os.path.exists(python)) + @requireVenvCreate def test_multiprocessing(self): """ diff --git a/Lib/venv/__init__.py b/Lib/venv/__init__.py index 19eddde700bcf9..cf8d12ec943bde 100644 --- a/Lib/venv/__init__.py +++ b/Lib/venv/__init__.py @@ -267,6 +267,8 @@ def symlink_or_copy(self, src, dst, relative_symlinks_ok=False): switch to a different set of files instead.) """ assert os.name != 'nt' + if os.path.islink(dst) and not os.path.exists(dst): + os.unlink(dst) force_copy = not self.symlinks if not force_copy: try: diff --git a/Misc/NEWS.d/next/Library/2026-01-12-22-24-28.gh-issue-143768.RbLnkFx.rst b/Misc/NEWS.d/next/Library/2026-01-12-22-24-28.gh-issue-143768.RbLnkFx.rst new file mode 100644 index 00000000000000..ce0f74f0f55a02 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-01-12-22-24-28.gh-issue-143768.RbLnkFx.rst @@ -0,0 +1,2 @@ +:mod:`venv`: Rebuild broken interpreter symlinks when upgrading a virtual +environment. Fix by Clay Dugo.