diff --git a/hermes_cli/_subprocess_compat.py b/hermes_cli/_subprocess_compat.py index 00358fd3bb79b..339adf87dafa0 100644 --- a/hermes_cli/_subprocess_compat.py +++ b/hermes_cli/_subprocess_compat.py @@ -36,6 +36,7 @@ __all__ = [ "IS_WINDOWS", "resolve_node_command", + "suppress_platform_ver_console", "windows_detach_flags", "windows_detach_flags_without_breakaway", "windows_hide_flags", @@ -226,6 +227,43 @@ def windows_hide_flags() -> int: return _CREATE_NO_WINDOW +def suppress_platform_ver_console() -> None: + """Stub out ``platform._syscmd_ver`` on Windows so it can never flash a + console window. No-op on non-Windows. + + CPython's ``platform.win32_ver()`` — reached by ``platform.uname()``, + ``platform.version()``, and ``platform.platform()`` — unconditionally + shells out ``cmd /c ver`` via ``subprocess.check_output(..., shell=True)`` + with no ``CREATE_NO_WINDOW``. From a windowless parent (the pythonw + gateway and every kanban worker it spawns) that allocates a fresh + *visible* console: one flashing ``cmd`` window per process, triggered by + any dependency that merely touches ``platform.uname()`` at import time. + + With ``_syscmd_ver`` stubbed to return its inputs, ``win32_ver()`` hits + the documented ``ValueError`` fallback and reads the version from + ``sys.getwindowsversion().platform_version`` — same information, queried + in-process, no subprocess, no window. Verified equivalent on + CPython 3.11 (``platform()`` → ``Windows-10-10.0.xxxxx-SP0`` either way). + + Call early, before heavyweight imports — the flash typically happens + during a dependency's import, not from Hermes' own code. + """ + if not IS_WINDOWS: + return + try: + import platform + + if hasattr(platform, "_syscmd_ver"): + def _quiet_syscmd_ver(system="", release="", version="", + supported_platforms=("win32", "win16", "dos")): + return system, release, version + + platform._syscmd_ver = _quiet_syscmd_ver + except Exception: + # Purely cosmetic hardening — never let it break startup. + pass + + def windows_detach_popen_kwargs() -> dict: """Return a dict of Popen kwargs that detach a child on Windows and fall back to the POSIX equivalent (``start_new_session=True``) on diff --git a/hermes_cli/main.py b/hermes_cli/main.py index fcca52b8851eb..c43174b65e064 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -61,6 +61,15 @@ except ModuleNotFoundError: pass +# Windows: neutralize CPython's ``platform._syscmd_ver`` before anything else +# imports — it shells out ``cmd /c ver`` (shell=True, no CREATE_NO_WINDOW), so +# any dependency touching ``platform.uname()`` at import time flashes a +# visible console when this process is windowless (pythonw gateway + every +# kanban worker). No-op on POSIX; never raises. +from hermes_cli._subprocess_compat import suppress_platform_ver_console + +suppress_platform_ver_console() + import os import sys diff --git a/tests/test_windows_subprocess_no_window_flags.py b/tests/test_windows_subprocess_no_window_flags.py index 3c99bdb50558c..8ae181f654b6b 100644 --- a/tests/test_windows_subprocess_no_window_flags.py +++ b/tests/test_windows_subprocess_no_window_flags.py @@ -941,3 +941,154 @@ def fake_run(cmd, **kwargs): assert kwargs["creationflags"] == _CREATE_NO_WINDOW assert kwargs["stdin"] == subprocess.DEVNULL assert kwargs["capture_output"] is True + + +# ── #67690 env probes, lazy installs, platform.win32_ver() (@m4r13y) ─────── +# +# Windowless processes (pythonw gateway + kanban workers) flashed consoles +# from three more spawn families: tools/env_probe._run's interpreter/pip +# probes, tools/lazy_deps' uv→pip→ensurepip install ladder, and CPython +# 3.11/3.12's platform.win32_ver() which shells out `cmd /c ver` with +# shell=True and no CREATE_NO_WINDOW. All are hide-only (creationflags); +# win32_ver is neutralized by stubbing platform._syscmd_ver so the +# documented ValueError fallback reads sys.getwindowsversion() instead. + + +def test_env_probe_run_hides_console_window(monkeypatch): + from tools import env_probe + + captured = [] + + def fake_run(cmd, **kwargs): + captured.append((cmd, kwargs)) + return _Completed(stdout="", returncode=0) + + monkeypatch.setattr(env_probe, "windows_hide_flags", lambda: _CREATE_NO_WINDOW) + monkeypatch.setattr(env_probe.subprocess, "run", fake_run) + + rc, out, err = env_probe._run(["python3", "--version"], timeout=1.0) + + assert rc == 0 + assert len(captured) == 1, captured + cmd, kwargs = captured[0] + assert cmd == ["python3", "--version"] + assert kwargs["creationflags"] == _CREATE_NO_WINDOW + # The temp-file capture contract (#67964) must survive: stdout/stderr are + # file objects (not PIPE) so a lingering grandchild can't wedge the probe. + assert kwargs["stdout"] is not None and kwargs["stdout"] != subprocess.PIPE + assert kwargs["stderr"] is not None and kwargs["stderr"] != subprocess.PIPE + assert kwargs["stdin"] == subprocess.DEVNULL + + +def test_lazy_deps_uv_install_hides_console_window(monkeypatch): + from tools import lazy_deps + + captured = [] + + def fake_run(cmd, **kwargs): + captured.append((cmd, kwargs)) + return _Completed(stdout="installed", returncode=0) + + monkeypatch.delenv(lazy_deps._LAZY_TARGET_ENV, raising=False) + monkeypatch.setattr(lazy_deps, "windows_hide_flags", lambda: _CREATE_NO_WINDOW) + monkeypatch.setattr(lazy_deps.subprocess, "run", fake_run) + monkeypatch.setattr(lazy_deps.shutil, "which", lambda name: "/usr/bin/uv" if name == "uv" else None) + + res = lazy_deps._venv_pip_install(("left-pad",)) + + assert res.success + spawns = _spawns(captured, "pip", "install", "left-pad") + assert len(spawns) == 1, captured + cmd, kwargs = spawns[0] + assert cmd[:3] == ["/usr/bin/uv", "pip", "install"] + assert kwargs["creationflags"] == _CREATE_NO_WINDOW + assert kwargs["stdin"] == subprocess.DEVNULL + + +def test_lazy_deps_pip_probe_and_install_hide_console_window(monkeypatch): + """No uv: the pip --version probe and the pip install fallback both hide.""" + from tools import lazy_deps + + captured = [] + + def fake_run(cmd, **kwargs): + captured.append((cmd, kwargs)) + return _Completed(stdout="pip 25.0", returncode=0) + + monkeypatch.delenv(lazy_deps._LAZY_TARGET_ENV, raising=False) + monkeypatch.setattr(lazy_deps, "windows_hide_flags", lambda: _CREATE_NO_WINDOW) + monkeypatch.setattr(lazy_deps.subprocess, "run", fake_run) + monkeypatch.setattr(lazy_deps.shutil, "which", lambda name: None) + + res = lazy_deps._venv_pip_install(("left-pad",)) + + assert res.success + probes = _spawns(captured, "-m", "pip", "--version") + installs = _spawns(captured, "-m", "pip", "install", "left-pad") + assert len(probes) == 1 and len(installs) == 1, captured + for _cmd, kwargs in probes + installs: + assert kwargs["creationflags"] == _CREATE_NO_WINDOW + assert kwargs["stdin"] == subprocess.DEVNULL + + +def test_lazy_deps_ensurepip_hides_console_window(monkeypatch): + """Failed pip probe: the ensurepip bootstrap spawn hides too.""" + from tools import lazy_deps + + captured = [] + + def fake_run(cmd, **kwargs): + captured.append((cmd, kwargs)) + if "--version" in cmd: + return _Completed(stdout="", returncode=1) # probe fails → ensurepip + return _Completed(stdout="ok", returncode=0) + + monkeypatch.delenv(lazy_deps._LAZY_TARGET_ENV, raising=False) + monkeypatch.setattr(lazy_deps, "windows_hide_flags", lambda: _CREATE_NO_WINDOW) + monkeypatch.setattr(lazy_deps.subprocess, "run", fake_run) + monkeypatch.setattr(lazy_deps.shutil, "which", lambda name: None) + + res = lazy_deps._venv_pip_install(("left-pad",)) + + assert res.success + bootstraps = _spawns(captured, "-m", "ensurepip", "--upgrade") + assert len(bootstraps) == 1, captured + assert bootstraps[0][1]["creationflags"] == _CREATE_NO_WINDOW + + +def test_suppress_platform_ver_console_posix_noop(monkeypatch): + """On POSIX the helper must do nothing at all and never raise.""" + import platform + + from hermes_cli import _subprocess_compat + + monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", False) + original = platform._syscmd_ver + + _subprocess_compat.suppress_platform_ver_console() + + assert platform._syscmd_ver is original + # win32_ver stays functional (returns empty fields off Windows). + assert platform.win32_ver() == ("", "", "", "") + + +def test_suppress_platform_ver_console_stubs_syscmd_ver(monkeypatch): + """Simulated Windows: _syscmd_ver is replaced by an in-process echo stub + so win32_ver() takes its ValueError fallback instead of `cmd /c ver`.""" + import platform + + from hermes_cli import _subprocess_compat + + monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", True) + # Register the original with monkeypatch so it gets restored after. + monkeypatch.setattr(platform, "_syscmd_ver", platform._syscmd_ver) + + _subprocess_compat.suppress_platform_ver_console() + + # The stub echoes its inputs — win32_ver() treats the unparseable value + # as the documented ValueError path and falls back to + # sys.getwindowsversion().platform_version (no subprocess, no window). + assert platform._syscmd_ver("s", "r", "v") == ("s", "r", "v") + # Idempotent + never raises on repeat calls. + _subprocess_compat.suppress_platform_ver_console() + assert platform._syscmd_ver() == ("", "", "") diff --git a/tools/env_probe.py b/tools/env_probe.py index 95427cc1c9af5..6e28a51cfd2dd 100644 --- a/tools/env_probe.py +++ b/tools/env_probe.py @@ -38,6 +38,8 @@ import threading from typing import Optional +from hermes_cli._subprocess_compat import windows_hide_flags + logger = logging.getLogger(__name__) # Module-level cache. The probe result is deterministic for the @@ -105,6 +107,11 @@ def _run(cmd: list[str], timeout: float = 3.0) -> tuple[int, str, str]: timeout=timeout, check=False, stdin=subprocess.DEVNULL, + # CREATE_NO_WINDOW (0 on POSIX): the probe runs in + # windowless processes (pythonw gateway / kanban workers) + # where a console child would otherwise flash a visible + # window per probe — ~5 flashes at every worker startup. + creationflags=windows_hide_flags(), ) except subprocess.TimeoutExpired: return -1, "", "timeout" diff --git a/tools/lazy_deps.py b/tools/lazy_deps.py index ec5692ecd5507..9d04e13503df8 100644 --- a/tools/lazy_deps.py +++ b/tools/lazy_deps.py @@ -79,6 +79,8 @@ from pathlib import Path from typing import Any, Callable, Optional +from hermes_cli._subprocess_compat import windows_hide_flags + logger = logging.getLogger(__name__) @@ -668,6 +670,7 @@ def _venv_pip_install(specs: tuple[str, ...], *, timeout: int = 300) -> _Install [uv_bin, "pip", "install", *target_args, *constraint_args, *specs], capture_output=True, text=True, timeout=timeout, env=uv_env, stdin=subprocess.DEVNULL, + creationflags=windows_hide_flags(), ) if r.returncode == 0: if target is not None: @@ -684,6 +687,7 @@ def _venv_pip_install(specs: tuple[str, ...], *, timeout: int = 300) -> _Install pip_cmd + ["--version"], capture_output=True, text=True, timeout=15, stdin=subprocess.DEVNULL, + creationflags=windows_hide_flags(), ) if probe.returncode != 0: raise FileNotFoundError("pip not in venv") @@ -693,6 +697,7 @@ def _venv_pip_install(specs: tuple[str, ...], *, timeout: int = 300) -> _Install [sys.executable, "-m", "ensurepip", "--upgrade", "--default-pip"], capture_output=True, text=True, timeout=120, check=True, stdin=subprocess.DEVNULL, + creationflags=windows_hide_flags(), ) except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e: return _InstallResult(False, "", @@ -703,6 +708,7 @@ def _venv_pip_install(specs: tuple[str, ...], *, timeout: int = 300) -> _Install pip_cmd + ["install", *target_args, *constraint_args, *specs], capture_output=True, text=True, timeout=timeout, stdin=subprocess.DEVNULL, + creationflags=windows_hide_flags(), ) if r.returncode == 0 and target is not None: _activate_target_on_syspath(target)