diff --git a/nanobot/process_runtime.py b/nanobot/process_runtime.py index 366963dac..b05514b12 100644 --- a/nanobot/process_runtime.py +++ b/nanobot/process_runtime.py @@ -351,7 +351,10 @@ class ManagedProcessRuntime(Generic[_StartOptionsT]): return process_is_running(pid, platform_name=self.platform_name) def _process_identity(self, pid: int) -> str | int | None: - if self.platform_name == "Windows": + # Process inspection must follow the host API even when tests inject a + # target platform. On Windows, falling through to POSIX calls is not + # merely unsupported: ``os.kill(pid, 0)`` broadcasts CTRL_C_EVENT. + if _platform_name() == "Windows" or self.platform_name == "Windows": return _windows_process_identity(pid) try: process_group = os.getpgid(pid) @@ -441,7 +444,8 @@ def process_is_running(pid: int, *, platform_name: str | None = None) -> bool: """Probe a PID without delivering a control event on Windows.""" if pid <= 0: return False - if (platform_name or _platform_name()) == "Windows": + host_platform = _platform_name() + if host_platform == "Windows" or platform_name == "Windows": # On Windows ``os.kill(pid, 0)`` sends CTRL_C_EVENT (whose value is 0) # instead of performing the harmless POSIX existence probe. return _windows_process_identity(pid) is not None diff --git a/tests/gateway/test_runtime.py b/tests/gateway/test_runtime.py index cb24e5e5d..d1b6e642d 100644 --- a/tests/gateway/test_runtime.py +++ b/tests/gateway/test_runtime.py @@ -440,6 +440,37 @@ def test_windows_process_probe_never_sends_ctrl_c(monkeypatch): assert process_is_running(54321, platform_name="Windows") is False +def test_windows_host_probe_stays_safe_when_target_platform_is_posix(monkeypatch): + monkeypatch.setattr("nanobot.process_runtime._platform_name", lambda: "Windows") + monkeypatch.setattr( + "nanobot.process_runtime._windows_process_identity", + lambda pid: "created-at" if pid == 12345 else None, + ) + monkeypatch.setattr( + "nanobot.process_runtime.os.kill", + lambda *_args: pytest.fail("Windows process probes must not call os.kill(pid, 0)"), + ) + + assert process_is_running(12345, platform_name="Linux") is True + assert process_is_running(54321, platform_name="Darwin") is False + + +def test_windows_host_identity_stays_safe_when_target_platform_is_posix(tmp_path, monkeypatch): + runtime = GatewayRuntime(paths=_paths(tmp_path), platform_name="Linux") + monkeypatch.setattr("nanobot.process_runtime._platform_name", lambda: "Windows") + monkeypatch.setattr( + "nanobot.process_runtime._windows_process_identity", + lambda pid: "created-at" if pid == 12345 else None, + ) + monkeypatch.setattr( + "nanobot.process_runtime.os.getpgid", + lambda *_args: pytest.fail("Windows process identities must not use POSIX APIs"), + raising=False, + ) + + assert runtime.process_identity(12345) == "created-at" + + def test_status_clears_stale_state(tmp_path, monkeypatch): runtime = GatewayRuntime(paths=_paths(tmp_path), platform_name="Linux") runtime.paths.run_dir.mkdir(parents=True)