diff --git a/Lib/test/test_getpath.py b/Lib/test/test_getpath.py index 83f09f3495547ac..c545858e639835f 100644 --- a/Lib/test/test_getpath.py +++ b/Lib/test/test_getpath.py @@ -290,6 +290,32 @@ def test_normal_posix(self): actual = getpath(ns, expected) self.assertEqual(expected, actual) + def test_relative_path_env_posix(self): + "Resolving the executable from a relative PATH entry yields an absolute path (gh-154160)" + ns = MockPosixNamespace( + PREFIX="/usr", + argv0="python", + ENV_PATH="usr/bin", + ) + ns.add_known_xfile("usr/bin/python") + ns.add_known_xfile("/Absolute/usr/bin/python") + ns.add_known_file("/usr/lib/python9.8/os.py") + ns.add_known_dir("/usr/lib/python9.8/lib-dynload") + expected = dict( + executable="/Absolute/usr/bin/python", + base_executable="/Absolute/usr/bin/python", + prefix="/usr", + exec_prefix="/usr", + module_search_paths_set=1, + module_search_paths=[ + "/usr/lib/python98.zip", + "/usr/lib/python9.8", + "/usr/lib/python9.8/lib-dynload", + ], + ) + actual = getpath(ns, expected) + self.assertEqual(expected, actual) + def test_buildpath_posix(self): """Test an in-build-tree layout on POSIX. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-19-18-30-00.gh-issue-154160.XnXeqS.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-19-18-30-00.gh-issue-154160.XnXeqS.rst new file mode 100644 index 000000000000000..d51bea4221f942a --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-19-18-30-00.gh-issue-154160.XnXeqS.rst @@ -0,0 +1,3 @@ +Fixed :data:`sys.executable` being a relative path when the interpreter was +found through a relative entry in :envvar:`PATH`. It is now made absolute, as +documented. diff --git a/Modules/getpath.py b/Modules/getpath.py index 6199567bd777aa0..32a64feef1ee6ca 100644 --- a/Modules/getpath.py +++ b/Modules/getpath.py @@ -285,7 +285,9 @@ def search_up(prefix, *landmarks, test=isfile): for p in ENV_PATH.split(DELIM): p = joinpath(p, program_name) if isxfile(p): - executable = p + # A relative PATH entry would otherwise leave executable + # relative, contrary to the documented behaviour (gh-154160). + executable = abspath(p) break if not executable: