From d872d7101f1c3a91e27a7b6767cccdd63cfa908c Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Fri, 10 Jul 2026 18:49:06 -0700 Subject: [PATCH] =?UTF-8?q?#684=20(devpulse):=20registry.py=20is=5Fpid=5Fa?= =?UTF-8?q?live=20Windows-guards=20its=20os.kill(pid,0)=20=E2=80=94=20win3?= =?UTF-8?q?2=20early-returns=20to=20OpenProcess+GetExitCodeProcess=20(os.k?= =?UTF-8?q?ill(pid,0)=3DTerminateProcess=20on=20Windows,=20KILLS=20the=20t?= =?UTF-8?q?arget).=20#682=20checker=20no=20longer=20flags=20it;=2026=20reg?= =?UTF-8?q?istry=20tests=20green.=20git=5Flock=5Ftool.py:120=20deferred=20?= =?UTF-8?q?to=20a=20separate=20cleanup=20(pre-existing=20tool=20debt:=2042?= =?UTF-8?q?=20prints/no-meta/architecture=20fail=20the=20gate).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apps/handlers/watchdog/registry.py | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/aipass/devpulse/apps/handlers/watchdog/registry.py b/src/aipass/devpulse/apps/handlers/watchdog/registry.py index a237dbf7..60b95f8d 100644 --- a/src/aipass/devpulse/apps/handlers/watchdog/registry.py +++ b/src/aipass/devpulse/apps/handlers/watchdog/registry.py @@ -173,10 +173,45 @@ def _is_zombie_linux(pid: int) -> bool: return False +def _pid_alive_windows(pid: int) -> bool: + """Windows-safe liveness via OpenProcess + GetExitCodeProcess (mirrors agent.py).""" + import ctypes + from ctypes import wintypes + + PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 + STILL_ACTIVE = 259 + + kernel32 = ctypes.windll.kernel32 # type: ignore[attr-defined] # Windows-only + kernel32.OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD] + kernel32.OpenProcess.restype = wintypes.HANDLE + kernel32.GetExitCodeProcess.argtypes = [wintypes.HANDLE, ctypes.POINTER(wintypes.DWORD)] + kernel32.GetExitCodeProcess.restype = wintypes.BOOL + kernel32.CloseHandle.argtypes = [wintypes.HANDLE] + kernel32.CloseHandle.restype = wintypes.BOOL + + handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid) + if not handle: + return False + try: + exit_code = wintypes.DWORD() + if not kernel32.GetExitCodeProcess(handle, ctypes.byref(exit_code)): + return False + return exit_code.value == STILL_ACTIVE + finally: + kernel32.CloseHandle(handle) + + def is_pid_alive(pid: int) -> bool: - """Return True if the process exists and is not a zombie.""" + """Return True if the process exists and is not a zombie. Windows-safe.""" if not isinstance(pid, int) or pid <= 0: return False + if sys.platform == "win32": + # os.kill(pid, 0) TERMINATES the target on Windows — use OpenProcess. + try: + return _pid_alive_windows(pid) + except Exception as exc: + logger.info("[watchdog.registry] PID %s Windows check failed (assuming alive): %s", pid, exc) + return True try: os.kill(pid, 0) except ProcessLookupError as exc: