From 4fc89825ea3185dc4b1689292d752ca0805cd0d9 Mon Sep 17 00:00:00 2001 From: Philip DePetro Date: Fri, 14 Aug 2026 10:23:45 -0700 Subject: [PATCH 1/3] Check against sys.executable when determining if we should instrument subprocess Native python binaries might not include 'python' in the file basename, causing the multiprocess monkey-patching code in pydevd to not recognize its subprocesses as python processes. Add a fallback check against sys.executable in is_python() so that subprocesses launched via the same executable are correctly instrumented for debugging. This is useful for custom Python builds or embedded distributions where the executable name doesn't contain 'python', 'jython', or 'pypy'. --- src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py b/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py index 09fcadec..814a7685 100644 --- a/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py +++ b/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py @@ -300,6 +300,9 @@ def is_python(path) -> bool: if filename.find(name) != -1: return True + if path == sys.executable: + return True + return False From 50e7b205f3c4ec3485a23dd48a4d1a88732aee60 Mon Sep 17 00:00:00 2001 From: Philip DePetro Date: Fri, 14 Aug 2026 10:50:46 -0700 Subject: [PATCH 2/3] Check against sys.executable when determining if we should instrument subprocess Native python binaries might not include 'python' in the file basename, causing the multiprocess monkey-patching code in pydevd to not recognize its subprocesses as python processes. Add a fallback check against sys.executable in is_python() so that subprocesses launched via the same executable are correctly instrumented for debugging. This handles executable identity rather than literal string equality: - exact match fast path - os.path.samefile when both paths exist (covers symlinks/hardlinks) - normalized realpath + normcase comparison for relative paths and Windows case-insensitivity This is useful for custom Python builds or embedded distributions where the executable name doesn't contain 'python', 'jython', or 'pypy', and where the same binary may be invoked via symlink or different case spelling. --- .../pydevd/_pydev_bundle/pydev_monkey.py | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py b/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py index 814a7685..ee62e46c 100644 --- a/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py +++ b/src/debugpy/_vendored/pydevd/_pydev_bundle/pydev_monkey.py @@ -300,8 +300,41 @@ def is_python(path) -> bool: if filename.find(name) != -1: return True - if path == sys.executable: - return True + # Fallback: check if the path refers to the same executable as sys.executable. + # This handles cases where the binary name doesn't contain "python" (e.g. + # custom builds, embedded distributions). We use a platform-aware + # comparison that covers symlinks, relative paths, and Windows case + # differences. + try: + path_str = path.decode(sys.getfilesystemencoding()) if isinstance(path, bytes) else path + sys_exec_str = sys.executable + + # Fast path: exact string match + if path_str == sys_exec_str: + return True + + # If both exist, samefile is the most reliable (handles symlinks, hardlinks) + try: + if os.path.exists(path_str) and os.path.exists(sys_exec_str): + if os.path.samefile(path_str, sys_exec_str): + return True + except (OSError, AttributeError, NotImplementedError, ValueError): + pass + + # Fallback: compare normalized realpaths + # - realpath resolves symlinks + # - abspath is implied by realpath, but we ensure it + # - normpath collapses redundant separators + # - normcase normalizes case and separators on Windows + try: + norm_path = os.path.normcase(os.path.normpath(os.path.realpath(path_str))) + norm_sys = os.path.normcase(os.path.normpath(os.path.realpath(sys_exec_str))) + if norm_path == norm_sys: + return True + except Exception: + pass + except Exception: + pass return False From d02ee146ce8a0988b3d97d281aaf71719216bb6a Mon Sep 17 00:00:00 2001 From: Philip DePetro Date: Fri, 14 Aug 2026 11:19:13 -0700 Subject: [PATCH 3/3] Add regression tests for is_python sys.executable identity check Covers: - exact sys.executable match - bytes version - symlink with non-python name (e.g. myapp -> python) - relative path - Windows case-insensitivity via normcase (simulated) - non-python binaries should return False - traditional basename detection still works --- .../pydevd/tests_python/test_pydev_monkey.py | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_pydev_monkey.py b/src/debugpy/_vendored/pydevd/tests_python/test_pydev_monkey.py index 55adac33..2307f8bf 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/test_pydev_monkey.py +++ b/src/debugpy/_vendored/pydevd/tests_python/test_pydev_monkey.py @@ -512,3 +512,76 @@ def test_monkey_patch_args_c_with_bytes() -> None: # The result should contain our patched debugpy setup somewhere result_str: str = b"".join(item.encode() if isinstance(item, str) else item for item in result).decode() assert "pydevd.settrace" in result_str + + +def test_is_python_sys_executable_exact(): + # Exact sys.executable should be considered python even if basename doesn't contain "python" + assert pydev_monkey.is_python(sys.executable) is True + + +def test_is_python_sys_executable_bytes(): + # Bytes version of sys.executable + assert pydev_monkey.is_python(sys.executable.encode()) is True + + +def test_is_python_sys_executable_symlink(tmp_path): + # Same binary invoked through a symlink with non-python name should still be recognized + # e.g. custom native binary named "myapp" that is a symlink to python + import os + + link_path = tmp_path / "myapp" + try: + os.symlink(sys.executable, str(link_path)) + except (OSError, NotImplementedError): + pytest.skip("Symlinks not supported on this platform") + + assert pydev_monkey.is_python(str(link_path)) is True + + +def test_is_python_sys_executable_relative(tmp_path): + # Relative path to sys.executable should be recognized + import os + + rel = os.path.relpath(sys.executable, start=str(tmp_path)) + # Change cwd to tmp_path to make relative resolution work for realpath + old_cwd = os.getcwd() + try: + os.chdir(str(tmp_path)) + assert pydev_monkey.is_python(rel) is True + finally: + os.chdir(old_cwd) + + +def test_is_python_sys_executable_normcase(): + # On Windows, normcase makes comparison case-insensitive. On POSIX, normcase is no-op, + # but realpath+samefile already covers identity. This test ensures case variation + # doesn't break on POSIX and would work on Windows. + import os + + upper = sys.executable.upper() + # If file system is case-insensitive or normcase normalizes, this should be True. + # On case-sensitive POSIX, upper won't exist, so samefile will fail and normcase + # won't match, but we at least ensure the function doesn't crash and returns a bool. + # To simulate Windows behavior, directly test normcase equivalence logic. + norm_upper = os.path.normcase(os.path.normpath(os.path.realpath(upper))) + norm_sys = os.path.normcase(os.path.normpath(os.path.realpath(sys.executable))) + if os.path.exists(upper) or norm_upper == norm_sys: + # Only assert True when the OS would actually consider them same + assert pydev_monkey.is_python(upper) is True + else: + # On case-sensitive systems, upper path likely doesn't exist, so should be False + # unless it accidentally matches via other heuristics - just ensure bool + assert isinstance(pydev_monkey.is_python(upper), bool) + + +def test_is_python_non_python(): + assert pydev_monkey.is_python("/bin/ls") is False + assert pydev_monkey.is_python("/usr/bin/myapp") is False + + +def test_is_python_name_contains_python(): + # Traditional detection via basename still works + assert pydev_monkey.is_python("/usr/bin/python3") is True + assert pydev_monkey.is_python("/usr/bin/python3.12") is True + assert pydev_monkey.is_python("C:\\Python\\python.exe") is True + assert pydev_monkey.is_python("/opt/pypy/bin/pypy") is True