# NousResearch/hermes-agent · Windows console/terminal flash suppression fixes

## Open now

- 0 issues, 2 PRs open on GitHub.
- PR #75224: fix: hide WhatsApp adapter helper subprocesses on Windows
- PR #108309: fix(windows): add CREATE_BREAKAWAY_FROM_JOB to windows_hide_flags()

This cluster is focused on a single theme: preventing unwanted console window popups and flashes on Windows when background subprocesses are spawned. Members include bug reports, feature requests, and numerous incremental fixes targeting subprocess creation flags like CREATE_NO_WINDOW and CREATE_BREAKAWAY_FROM_JOB across various spawn sites (git, ffmpeg, netstat, gateway, etc.).

## TL;DR

- 45 member(s): 0 open issue(s), 24 closed issue(s), 2 open PR(s), 4 merged PR(s), 15 PR(s) closed without merging.
- A fix was merged to main in PR #54417. Verify it addresses your case.
- **Recommended:** read PR #54417 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `unresolved`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
- Members outside that structural core may belong to a different underlying problem.
- Pairwise link density `0.04`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #24073: \[Feature\]: Hidden Terminal exec on Windows Native
- #42544: Windows: terminal tool flashes cmd window on every command execution
- #43848: \[Feature\]: Set the "CREATE_NO_WINDOW" flag for Window terminal process
- #49851: Windows: terminal tool creates visible bash/cmd windows that steal focus
- #52310: \[Bug\]: Black console window flashes briefly whenever terminal spawns bash on Windows
- #52788: bug(desktop): CMD console window flashes on every terminal command on Windows
- #52981: \[Bug\]: Windows 11 Hermes Desktop — 频繁弹出黑色控制台窗口闪烁（git.exe / gh.exe / cmd.exe）
- #52982: \[Bug\]: Windows 11 Hermes Desktop — 频繁弹出黑色控制台窗口闪烁（git.exe / gh.exe / cmd.exe）
- #53014: Windows: terminal 工具强制使用 bash 导致频繁弹窗
- #53016: \[Bug\] Windows desktop app flashes cmd console window intermittently after update
- #53053: \[Bug\]: Subprocess calls flash black cmd.exe windows on Windows
- #53273: Windows Desktop GUI: CREATE_NO_WINDOW insufficient — cmd windows flash on every terminal() call
- #53282: Windows: Console window flashes on every subprocess call (missing CREATE_NO_WINDOW)
- #53342: \[Bug\]: The Hermes desktop client on Windows keeps flickering black command prompt windows nonstop, making the program completely inoperable. This is a critical blocking bug!
- #53424: \[Bug\] Windows: rapid conhost.exe window flashing due to missing CREATE_NO_WINDOW in subprocess calls
- #53751: Windows 10 desktop app triggers infinite PowerShell flash loop on launch
- #54323: windows_hide_flags() missing CREATE_BREAKAWAY_FROM_JOB — Desktop app console flash persists after #54236
- #54506: Command prompt keep blinking at screen when desktop app is open
- #55604: windows_hide_flags() missing CREATE_BREAKAWAY_FROM_JOB — cmd.exe windows flash in Electron desktop apps (Hermes One, etc.)
- #55627: \[Bug\]: OpenConsole.exe / conhost.exe / git.exe 进程无限闪烁弹窗 — Windows Electron 桌面版
- #55633: \[Bug\]: OpenConsole.exe / conhost.exe / git.exe processes infinitely flash and spawn — Windows Electron Desktop
- #56503: Windows subprocess console flashes — request for official fix
- #56747: \[Windows\] Blank terminal console windows flash when running agent or tools on desktop GUI
- #63698: \[Bug\]: Console windows flash/appear despite windows_hide_console: true

## Prior & competing PRs

- **#42605: fix(terminal): prevent Windows cmd flash (#42544)**. closed without merge · fix tier `suggested`
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `aa2ae36c3`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#44105: fix(windows): hide console-window flash on gateway-runtime subprocess spawns**. closed without merge · fix tier `suggested`
  - maintainer direction: category `superseded_by_pr`; implication `superseded`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#50183: fix(terminal): suppress console window flash on Windows via CREATE_NO_WINDOW (#49851, #42544)**. closed without merge · fix tier `suggested`
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `aa2ae36c3`, `#54220`, `#42544`, `#49851`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#53810: fix(windows): stop terminal-window popups from background spawns**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `apps/desktop/electron/main.cjs`, `cron/scheduler.py`, `gateway/run.py`, `gateway/status.py`, `hermes_bootstrap.py`, `hermes_cli/_subprocess_compat.py`, `hermes_cli/claw.py`, `hermes_cli/clipboard.py`, `hermes_cli/gateway.py`, `hermes_cli/gateway_windows.py`, `hermes_cli/main.py`, `hermes_cli/web_server.py`, `hermes_constants.py`, `plugins/platforms/discord/adapter.py`, `plugins/platforms/whatsapp/adapter.py`, `tests/gateway/test_status.py`, `tests/hermes_cli/test_claw.py`, `tests/hermes_cli/test_gateway.py`, `tests/hermes_cli/test_gateway_windows.py`, `tests/hermes_cli/test_web_server.py` (+13 more)
  - fix hunks:
  - `apps/desktop/electron/main.cjs`

    ```diff
    @@ -1565,18 +1565,17 @@ function readVenvHome(venvRoot) {
     function getNoConsoleVenvPython(venvRoot) {
       if (!IS_WINDOWS) return getVenvPython(venvRoot)
     
    -  // Prefer the venv's own pythonw shim — it carries pyvenv.cfg / site-packages
    -  // wiring. Falling back to the base uv/python.org pythonw.exe skips the venv
    -  // and breaks imports (yaml, hermes_cli, …) even when PYTHONPATH is patched.
    -  const venvPythonw = path.join(venvRoot, 'Scripts', 'pythonw.exe')
    -  if (fileExists(venvPythonw)) return venvPythonw
    -
    +  // uv venv launchers can re-exec console python.exe, which allocates conhost /
    +  // Windows Terminal. Use base pythonw directly and provide imports via env.
       const baseHome = readVenvHome(venvRoot)
       if (baseHome) {
         const basePythonw = path.join(baseHome, 'pythonw.exe')
         if (fileExists(basePythonw)) return basePythonw
       }
     
    +  const venvPythonw = path.join(venvRoot, 'Scripts', 'pythonw.exe')
    +  if (fileExists(venvPythonw)) return venvPythonw
    +
       return venvPythonw
     }
     
    ```

    ```diff
    @@ -2797,7 +2796,7 @@ function createPythonBackend(root, label, dashboardArgs, options = {}) {
         args: ['-m', 'hermes_cli.main', ...dashboardArgs],
         env: buildDesktopBackendEnv({
           hermesHome: HERMES_HOME,
    -      pythonPathEntries: [root],
    +      pythonPathEntries: [root, ...getVenvSitePackagesEntries(venvRoot)],
           venvRoot
         }),
         root,
    ```

    ```diff
    @@ -2821,7 +2820,7 @@ function createActiveBackend(dashboardArgs) {
         args: ['-m', 'hermes_cli.main', ...dashboardArgs],
         env: buildDesktopBackendEnv({
           hermesHome: HERMES_HOME,
    -      pythonPathEntries: [ACTIVE_HERMES_ROOT],
    +      pythonPathEntries: [ACTIVE_HERMES_ROOT, ...getVenvSitePackagesEntries(VENV_ROOT)],
           venvRoot: VENV_ROOT
         }),
         root: ACTIVE_HERMES_ROOT,
    ```
  - `cron/scheduler.py`

    ```diff
    @@ -3046,4 +3046,11 @@ def _on_done(_f: concurrent.futures.Future) -> None:
     
     
     if __name__ == "__main__":
    +    # Standalone background scheduler: drop any console a uv pythonw→python
    +    # re-exec auto-allocated. No-op on POSIX / when run in-gateway.
    +    try:
    +        import hermes_bootstrap
    +        hermes_bootstrap.detach_orphan_console()
    +    except Exception:
    +        pass
         tick(verbose=True)
    ```
  - `gateway/run.py`

    ```diff
    @@ -18593,6 +18593,13 @@ def restart_signal_handler():
     
     def main():
         """CLI entry point for the gateway."""
    +    # Background daemon: drop any console auto-allocated by a uv pythonw→python
    +    # re-exec so no terminal lingers. No-op on POSIX / when already detached.
    +    try:
    +        hermes_bootstrap.detach_orphan_console()
    +    except Exception:
    +        pass
    +
         # Force UTF-8 stdio on Windows — gateway logs and startup banner would
         # otherwise UnicodeEncodeError on cp1252 consoles.  No-op on POSIX.
         try:
    ```
  - `gateway/status.py`

    ```diff
    @@ -81,8 +81,10 @@ def terminate_pid(pid: int, *, force: bool = False) -> None:
         because os.kill(..., SIGTERM) is not equivalent to a tree-killing hard stop.
         """
         if force and _IS_WINDOWS:
    +        from hermes_cli import _subprocess_compat
    +
             try:
    -            result = subprocess.run(
    +            result = _subprocess_compat.run(
                     ["taskkill", "/PID", str(pid), "/T", "/F"],
                     capture_output=True,
                     text=True,
    ```
  - `hermes_bootstrap.py`

    ```diff
    @@ -80,6 +80,26 @@ def apply_windows_utf8_bootstrap() -> bool:
         os.environ.setdefault("PYTHONUTF8", "1")
         os.environ.setdefault("PYTHONIOENCODING", "utf-8")
     
    +    # Python's platform.win32_ver()/platform.platform() can shell out to
    +    # ``cmd.exe /c ver`` on Windows.  In pythonw-launched background processes
    +    # that still creates a visible terminal handoff on machines where Windows
    +    # Terminal is the default console host.  Disable that subprocess path early.
    +    try:
    +        import platform
    +
    +        def _no_subprocess_syscmd_ver(
    +            system: str = "",
    +            release: str = "",
    +            version: str = "",
    +            *_args,
    +            **_kwargs,
    +        ) -> tuple[str, str, str]:
    +            return system or "Windows", release, version
    +
    +        platform._syscmd_ver = _no_subprocess_syscmd_ver  # type: ignore[attr-defined]
    +    except Exception:
    +        pass
    +
         # 2. Reconfigure the current process's stdio to UTF-8.  Needed
         #    because os.environ changes don't retroactively rebind sys.stdout
         #    — those were bound at interpreter startup based on the console
    ```

    ```diff
    @@ -122,6 +142,44 @@ def apply_windows_utf8_bootstrap() -> bool:
         return True
     
     
    +def detach_orphan_console() -> bool:
    +    """Free a console window that was auto-allocated for this process alone.
    +
    +    Background-only entry points (gateway daemon, dashboard backend, cron
    +    runner, TUI/desktop stdio backends) call this explicitly. uv-created venvs
    +    ship a ``Scripts\\pythonw.exe`` redirector that re-execs the *base* console
    +    ``python.exe``; that re-exec allocates its own conhost/Windows Terminal
    +    window even though the launcher wanted no console. We drop it so nothing
    +    lingers.
    +
    +    This is NOT wired into the import-time bootstrap on purpose: the discriminator
    +    (``GetConsoleProcessList() == 1``) cannot tell a phantom console apart from a
    +    user who deliberately opened the *interactive* CLI/TUI in its own fresh
    +    console (double-click, Start-menu shortcut, a ConPTY), since both report a
    +    single attached process with a tty. Intent is only knowable from the entry
    +    point — so only known-background mains call this, never the interactive CLI.
    +
    +    A properly detached daemon (``DETACHED_PROCESS``) has no console at all, so
    +    ``GetConsoleWindow()`` is NULL and this is a no-op. Returns True iff a console
    +    was actually freed. No-op (returns False) on non-Windows.
    +    """
    +    if not _IS_WINDOWS:
    +        return False
    +    try:
    +        import ctypes
    +
    +        kernel32 = ctypes.windll.kernel32
    +        if not kernel32.GetConsoleWindow():
    +            return False
    +        buf = (ctypes.c_uint * 4)()
    +        if kernel32.GetConsoleProcessList(buf, 4) == 1:
    +            kernel32.FreeConsole()
    +            return True
    +    except Exception:
    +        pass
    +    return False
    +
    +
     def harden_import_path(src_root: str | None = None) -> None:
         """Stop a package in the current directory from shadowing Hermes modules.
     
    ```
  - `hermes_cli/_subprocess_compat.py`

    ```diff
    @@ -28,12 +28,15 @@
     from __future__ import annotations
     
     import shutil
    +import subprocess
     import sys
     from typing import Sequence
     
     __all__ = [
         "IS_WINDOWS",
         "resolve_node_command",
    +    "run",
    +    "popen",
         "windows_detach_flags",
         "windows_detach_flags_without_breakaway",
         "windows_hide_flags",
    ```

    ```diff
    @@ -201,6 +204,44 @@ def windows_hide_flags() -> int:
         return _CREATE_NO_WINDOW
     
     
    +# -----------------------------------------------------------------------------
    +# The single chokepoint for spawning a process without a console window.
    +# -----------------------------------------------------------------------------
    +
    +
    +def _no_window(kwargs: dict) -> dict:
    +    """OR ``CREATE_NO_WINDOW`` into ``creationflags`` on Windows (no-op on POSIX).
    +
    +    Merges rather than overwrites, so a caller that needs detach semantics can
    +    pass ``creationflags=windows_detach_flags()`` and still go through here —
    +    ``CREATE_NO_WINDOW`` is already part of that bundle, so the OR is idempotent.
    +    """
    +    if IS_WINDOWS:
    +        kwargs["creationflags"] = kwargs.get("creationflags", 0) | _CREATE_NO_WINDOW
    +    return kwargs
    +
    +
    +def run(cmd, **kwargs):
    +    """``subprocess.run`` that never flashes a console window on Windows.
    +
    +    This is the primitive every Hermes spawn of a *console-subsystem* program
    +    (``taskkill``, ``schtasks``, ``agent-browser``, ``git-bash``, version
    +    probes, …) must use. Routing through one function makes "no visible
    +    terminal" structural instead of a per-call-site rule that gets forgotten —
    +    which is exactly how cron-driven and future spawns leaked windows before.
    +
    +    Python child processes are additionally covered by the ``FreeConsole``
    +    catch-all in :mod:`hermes_bootstrap`, but native exes can't run that, so the
    +    spawn-time flag here is the only thing that helps them.
    +    """
    +    return subprocess.run(cmd, **_no_window(kwargs))
    +
    +
    +def popen(cmd, **kwargs):
    +    """``subprocess.Popen`` counterpart of :func:`run` — see its docstring."""
    +    return subprocess.Popen(cmd, **_no_window(kwargs))
    +
    +
     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
    ```
  - `hermes_cli/claw.py`

    ```diff
    @@ -77,9 +77,11 @@ def _detect_openclaw_processes() -> list[str]:
     
         # -- process scan ------------------------------------------------------
         if sys.platform == "win32":
    +        from hermes_cli import _subprocess_compat
    +
             try:
                 for exe in ("openclaw.exe", "clawd.exe"):
    -                result = subprocess.run(
    +                result = _subprocess_compat.run(
                         ["tasklist", "/FI", f"IMAGENAME eq {exe}"],
                         capture_output=True, text=True, timeout=5,
                     )
    ```

    ```diff
    @@ -93,7 +95,7 @@ def _detect_openclaw_processes() -> list[str]:
                     'Where-Object { $_.CommandLine -match "openclaw|clawd" } | '
                     'Select-Object -First 1 ProcessId'
                 )
    -            result = subprocess.run(
    +            result = _subprocess_compat.run(
                     ["powershell", "-NoProfile", "-Command", ps_cmd],
                     capture_output=True, text=True, timeout=5,
                 )
    ```
  - `hermes_cli/clipboard.py`

    ```diff
    @@ -198,7 +198,9 @@ def _macos_osascript(dest: Path) -> bool:
     
     
     def _run_powershell(exe: str, script: str, timeout: int) -> subprocess.CompletedProcess:
    -    return subprocess.run(
    +    from hermes_cli import _subprocess_compat
    +
    +    return _subprocess_compat.run(
             [exe, "-NoProfile", "-NonInteractive", "-Command", script],
             capture_output=True, text=True, timeout=timeout,
         )
    ```

    ```diff
    @@ -254,9 +256,11 @@ def _powershell_save_image(exe: str, dest: Path, *, timeout: int, label: str) ->
     
     def _find_powershell() -> str | None:
         """Return the first available PowerShell executable, or None."""
    +    from hermes_cli import _subprocess_compat
    +
         for name in ("powershell", "pwsh"):
             try:
    -            r = subprocess.run(
    +            r = _subprocess_compat.run(
                     [name, "-NoProfile", "-NonInteractive", "-Command", "echo ok"],
                     capture_output=True, text=True, timeout=5,
                 )
    ```
  - `hermes_cli/gateway.py`

    ```diff
    @@ -380,6 +380,24 @@ def _matches_gateway_runtime(command: str) -> bool:
     
         try:
             if is_windows():
    +            try:
    +                import psutil  # type: ignore
    +
    +                for proc in psutil.process_iter(["pid", "cmdline"]):
    +                    pid = int(proc.info.get("pid") or 0)
    +                    if pid == os.getpid() or pid in exclude_pids:
    +                        continue
    +                    command = " ".join(proc.info.get("cmdline") or [])
    +                    if _matches_gateway_runtime(command) and (
    +                        all_profiles or _matches_current_profile(command)
    +                    ):
    +                        _append_unique_pid(pids, pid, exclude_pids)
    +                return _filter_venv_launcher_stubs(pids) if len(pids) > 1 else pids
    +            except Exception:
    +                pass
    +
    +            from hermes_cli import _subprocess_compat
    +
                 # Prefer wmic when present (fast, stable output format).  On
                 # modern Windows 11 / Win 10 late builds, wmic has been
                 # removed as part of the WMIC deprecation — fall back to
    ```

    ```diff
    @@ -390,7 +408,7 @@ def _matches_gateway_runtime(command: str) -> bool:
                 result = None
                 if wmic_path is not None:
                     try:
    -                    result = subprocess.run(
    +                    result = _subprocess_compat.run(
                             [
                                 wmic_path,
                                 "process",
    ```

    ```diff
    @@ -421,7 +439,7 @@ def _matches_gateway_runtime(command: str) -> bool:
                         "}"
                     )
                     try:
    -                    result = subprocess.run(
    +                    result = _subprocess_compat.run(
                             [powershell, "-NoProfile", "-Command", ps_cmd],
                             capture_output=True,
                             text=True,
    ```

    ```diff
    @@ -6632,7 +6650,6 @@ def _gateway_command_inner(args):
                 # path that can be reaped with the old gateway process.  If the
                 # Windows backend raises, intentionally preserve the existing
                 # generic failure fallback below.
    -            service_configured = gateway_windows.is_installed()
                 try:
                     gateway_windows.restart()
                     return
    ```
  - `hermes_cli/gateway_windows.py`

    ```diff
    @@ -39,10 +39,10 @@
     from pathlib import Path
     from xml.sax.saxutils import escape
     
    +from hermes_cli import _subprocess_compat
     from hermes_cli._subprocess_compat import (
         windows_detach_flags,
         windows_detach_flags_without_breakaway,
    -    windows_hide_flags,
     )
     
     # Short timeouts: schtasks occasionally wedges and we don't want to hang forever.
    ```

    ```diff
    @@ -157,7 +157,7 @@ def _exec_schtasks(args: list[str]) -> tuple[int, str, str]:
         if schtasks is None:
             return (1, "", "schtasks.exe not found on PATH")
         try:
    -        proc = subprocess.run(
    +        proc = _subprocess_compat.run(
                 [schtasks, *args],
                 capture_output=True,
                 text=True,
    ```

    ```diff
    @@ -168,10 +168,6 @@ def _exec_schtasks(args: list[str]) -> tuple[int, str, str]:
                 encoding=_schtasks_encoding(),
                 errors="replace",
                 timeout=_SCHTASKS_TIMEOUT_S,
    -            # CREATE_NO_WINDOW avoids a flashing console window when the CLI
    -            # is itself hosted in a TUI. See tools/browser_tool.py for the
    -            # same pattern and the windows-subprocess-sigint-storm.md ref.
    -            creationflags=windows_hide_flags(),
             )
             return (proc.returncode, proc.stdout or "", proc.stderr or "")
         except subprocess.TimeoutExpired:
    ```

    ```diff
    @@ -1605,7 +1601,17 @@ def stop() -> None:
             drained = _drain_gateway_pid(pid, _windows_stop_drain_timeout())
     
         stopped_any = drained
    -    if is_task_registered():
    +    has_service_artifact = (
    +        get_task_script_path().exists()
    +        or get_task_script_path().with_suffix(".vbs").exists()
    +        or get_startup_entry_path().exists()
    +        or _legacy_startup_entry_path().exists()
    +    )
    +    if (
    +        has_service_artifact
    +        and os.getenv("HERMES_NONINTERACTIVE") != "1"
    +        and is_task_registered()
    +    ):
             code, _out, err = _exec_schtasks(["/End", "/TN", get_task_name()])
             # schtasks returns nonzero when the task isn't currently running — don't treat that as an error.
             if code == 0:
    ```

    ```diff
    @@ -1673,7 +1679,8 @@ def restart() -> None:
     
         # Give Windows a moment to release the listening port.
         time.sleep(1.0)
    -    start()
    +    pid = _spawn_detached()
    +    _report_gateway_start(f"direct spawn (PID {pid})")
     
         if not _wait_for_gateway_ready(timeout_s=15.0):
             raise RuntimeError(
    ```
  - `hermes_cli/main.py`

    ```diff
    @@ -5730,6 +5730,8 @@ def _find_stale_dashboard_pids(
     
         try:
             if sys.platform == "win32":
    +            from hermes_cli import _subprocess_compat
    +
                 # wmic may emit text in the system code page (for example cp936
                 # on zh-CN systems), not UTF-8. In text mode, subprocess output
                 # decoding depends on Python's configuration (locale-dependent
    ```

    ```diff
    @@ -5737,7 +5739,7 @@ def _find_stale_dashboard_pids(
                 # here is errors="ignore": it prevents a reader-thread
                 # UnicodeDecodeError from leaving result.stdout=None and turning
                 # the later .split() into an AttributeError (#17049).
    -            result = subprocess.run(
    +            result = _subprocess_compat.run(
                     ["wmic", "process", "get", "ProcessId,CommandLine", "/FORMAT:LIST"],
                     capture_output=True,
                     text=True,
    ```

    ```diff
    @@ -5977,9 +5979,11 @@ def _kill_stale_dashboard_processes(
         failed: list[tuple[int, str]] = []
     
         if sys.platform == "win32":
    +        from hermes_cli import _subprocess_compat
    +
             for pid in pids:
                 try:
    -                result = subprocess.run(
    +                result = _subprocess_compat.run(
                         ["taskkill", "/PID", str(pid), "/F"],
                         capture_output=True,
                         text=True,
    ```
  - `hermes_cli/web_server.py`

    ```diff
    @@ -2481,16 +2481,52 @@ def _record_completed_action(name: str, message: str, exit_code: int = 1) -> Non
         _ACTION_RESULTS[name] = {"exit_code": exit_code, "pid": None}
     
     
    -def _dashboard_spawn_executable() -> str:
    -    """Prefer pythonw.exe for detached dashboard actions on Windows."""
    +def _dashboard_spawn_details() -> Tuple[str, Dict[str, str]]:
    +    """Return (executable, env overlay) for detached dashboard actions.
    +
    +    On Windows this mirrors the gateway's uv-safe detached launcher logic so
    +    action spawns do not regress to console python.exe (which creates a visible
    +    terminal window). Non-Windows callsites get the current interpreter and no
    +    env overlay.
    +    """
         if sys.platform != "win32":
    -        return sys.executable
    +        return sys.executable, {}
    +
         exe = sys.executable
    -    if exe.lower().endswith("python.exe"):
    -        pythonw = os.path.join(os.path.dirname(exe), "pythonw.exe")
    -        if os.path.isfile(pythonw):
    -            return pythonw
    -    return exe
    +    try:
    +        from hermes_cli.gateway_windows import _resolve_detached_python
    +
    +        venv_root = os.environ.get("VIRTUAL_ENV", "").strip()
    +        if not venv_root:
    +            for candidate in (PROJECT_ROOT / "venv", PROJECT_ROOT / ".venv"):
    +                if (candidate / "Scripts" / "python.exe").exists():
    +                    venv_root = str(candidate)
    +                    break
    +        probe_exe = (
    +            os.path.join(venv_root, "Scripts", "python.exe")
    +            if venv_root
    +            else exe
    +        )
    +        windowless_exe, venv_dir, extra_pythonpath = _resolve_detached_python(probe_exe)
    +        env_overlay: Dict[str, str] = {}
    +        if venv_dir:
    +            env_overlay["VIRTUAL_ENV"] = str(venv_dir)
    +            site_packages = Path(venv_dir) / "Lib" / "site-packages"
    +            if site_packages.exists() and str(site_packages) not in extra_pythonpath:
    +                extra_pythonpath = [*extra_pythonpath, str(site_packages)]
    +        if extra_pythonpath:
    +            existing = os.environ.get("PYTHONPATH", "")
    +            env_overlay["PYTHONPATH"] = os.pathsep.join(
    +                [*extra_pythonpath, existing] if existing else list(extra_pythonpath)
    +            )
    +        return windowless_exe, env_overlay
    +    except Exception:
    +        # Best-effort fallback: sibling pythonw keeps the legacy no-console path.
    +        if exe.lower().endswith("python.exe"):
    +            pythonw = os.path.join(os.path.dirname(exe), "pythonw.exe")
    +            if os.path.isfile(pythonw):
    +                return pythonw, {}
    +    return exe, {}
     
     
     def _spawn_hermes_action(subcommand: List[str], name: str) -> subprocess.Popen:
    ```

    ```diff
    @@ -2507,15 +2543,20 @@ def _spawn_hermes_action(subcommand: List[str], name: str) -> subprocess.Popen:
             f"\n=== {name} started {time.strftime('%Y-%m-%d %H:%M:%S')} ===\n".encode()
         )
     
    -    cmd = [_dashboard_spawn_executable(), "-m", "hermes_cli.main", *subcommand]
    +    spawn_executable, spawn_env_overlay = _dashboard_spawn_details()
    +    cmd = [spawn_executable, "-m", "hermes_cli.main", *subcommand]
     
         popen_kwargs: Dict[str, Any] = {
             "cwd": str(PROJECT_ROOT),
             "stdin": subprocess.DEVNULL,
             "stdout": log_file,
             "stderr": subprocess.STDOUT,
    -        "env": {**os.environ, "HERMES_NONINTERACTIVE": "1"},
    +        "env": {**os.environ, "HERMES_NONINTERACTIVE": "1", **spawn_env_overlay},
         }
    +    log_file.write(f"spawn executable: {spawn_executable}\n".encode())
    +    if spawn_env_overlay:
    +        keys = ",".join(sorted(spawn_env_overlay.keys()))
    +        log_file.write(f"spawn env overlay keys: {keys}\n".encode())
         if sys.platform == "win32":
             popen_kwargs["creationflags"] = windows_detach_flags()
         else:
    ```

    ```diff
    @@ -13547,6 +13588,15 @@ def start_server(
         — used when a profile alias (``<profile> dashboard``) routes to the
         machine dashboard.
         """
    +    # Desktop spawns this backend via a no-console venv python; a uv
    +    # pythonw→python re-exec can still auto-allocate a console. Drop it.
    +    # No-op on POSIX and when launched from an interactive shell.
    +    try:
    +        import hermes_bootstrap
    +        hermes_bootstrap.detach_orphan_console()
    +    except Exception:
    +        pass
    +
         import uvicorn
     
         try:
    ```
  - `hermes_constants.py`

    ```diff
    @@ -561,12 +561,15 @@ def agent_browser_runnable(path: str | None) -> bool:
             return False
         import subprocess
     
    +    from hermes_cli import _subprocess_compat
    +
         try:
    -        result = subprocess.run(
    +        result = _subprocess_compat.run(
                 [path, "--version"],
                 capture_output=True,
                 timeout=10,
                 env=with_hermes_node_path(),
    +            stdin=subprocess.DEVNULL,
             )
         except (OSError, subprocess.TimeoutExpired, ValueError):
             return False
    ```
  - `plugins/platforms/discord/adapter.py`

    ```diff
    @@ -665,7 +665,9 @@ def pcm_to_wav(pcm_data: bytes, output_path: str,
                 f.write(pcm_data)
                 pcm_path = f.name
             try:
    -            subprocess.run(
    +            from hermes_cli import _subprocess_compat
    +
    +            _subprocess_compat.run(
                     [
                         "ffmpeg", "-y", "-loglevel", "error",
                         "-f", "s16le",
    ```

    ```diff
    @@ -679,7 +681,6 @@ def pcm_to_wav(pcm_data: bytes, output_path: str,
                     check=True,
                     timeout=10,
                     stdin=subprocess.DEVNULL,
    -                creationflags=windows_hide_flags(),
                 )
             finally:
                 try:
    ```
  - `plugins/platforms/whatsapp/adapter.py`

    ```diff
    @@ -78,8 +78,10 @@ def _kill_port_process(port: int) -> None:
         """Kill any process *listening* on the given TCP port (a stale bridge)."""
         try:
             if _IS_WINDOWS:
    +            from hermes_cli import _subprocess_compat
    +
                 # Use netstat to find the PID bound to this port, then taskkill
    -            result = subprocess.run(
    +            result = _subprocess_compat.run(
                     ["netstat", "-ano", "-p", "TCP"],
                     capture_output=True, text=True, timeout=5,
                 )
    ```

    ```diff
    @@ -89,7 +91,7 @@ def _kill_port_process(port: int) -> None:
                         local_addr = parts[1]
                         if local_addr.endswith(f":{port}"):
                             try:
    -                            subprocess.run(
    +                            _subprocess_compat.run(
                                     ["taskkill", "/PID", parts[4], "/F"],
                                     capture_output=True, timeout=5,
                                 )
    ```

    ```diff
    @@ -207,11 +209,13 @@ def _write_bridge_pidfile(session_path: Path, pid: int) -> None:
     def _terminate_bridge_process(proc, *, force: bool = False) -> None:
         """Terminate the bridge process using process-tree semantics where possible."""
         if _IS_WINDOWS:
    +        from hermes_cli import _subprocess_compat
    +
             cmd = ["taskkill", "/PID", str(proc.pid), "/T"]
             if force:
                 cmd.append("/F")
             try:
    -            result = subprocess.run(
    +            result = _subprocess_compat.run(
                     cmd,
                     capture_output=True,
                     text=True,
    ```
  - `tests/gateway/test_status.py`

    ```diff
    @@ -618,20 +618,25 @@ def no_proc(self, *args, **kwargs):
     
     class TestTerminatePid:
         def test_force_uses_taskkill_on_windows(self, monkeypatch):
    +        from hermes_cli import _subprocess_compat
    +
             calls = []
             monkeypatch.setattr(status, "_IS_WINDOWS", True)
    +        monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", True)
     
    -        def fake_run(cmd, capture_output=False, text=False, timeout=None):
    -            calls.append((cmd, capture_output, text, timeout))
    +        def fake_run(cmd, capture_output=False, text=False, timeout=None, **kwargs):
    +            calls.append((cmd, capture_output, text, timeout, kwargs))
                 return SimpleNamespace(returncode=0, stdout="", stderr="")
     
             monkeypatch.setattr(status.subprocess, "run", fake_run)
     
             status.terminate_pid(123, force=True)
     
    -        assert calls == [
    -            (["taskkill", "/PID", "123", "/T", "/F"], True, True, 10)
    -        ]
    +        assert len(calls) == 1
    +        cmd, capture_output, text, timeout, kwargs = calls[0]
    +        assert cmd == ["taskkill", "/PID", "123", "/T", "/F"]
    +        assert (capture_output, text, timeout) == (True, True, 10)
    +        assert kwargs["creationflags"] & 0x08000000
     
         def test_force_falls_back_to_sigterm_when_taskkill_missing(self, monkeypatch):
             calls = []
    ```
  - `tests/hermes_cli/test_claw.py`

    ```diff
    @@ -725,8 +725,9 @@ def test_detects_systemd_service(self):
         def test_returns_match_on_windows_when_openclaw_exe_running(self):
             with patch.object(claw_mod, "sys") as mock_sys:
                 mock_sys.platform = "win32"
    -            with patch.object(claw_mod, "subprocess") as mock_subprocess:
    -                mock_subprocess.run.side_effect = [
    +            # Windows scans go through the hidden-spawn primitive (no console flash).
    +            with patch("hermes_cli._subprocess_compat.run") as mock_run:
    +                mock_run.side_effect = [
                         MagicMock(returncode=0, stdout="openclaw.exe                 1234 Console    1     45,056 K\n"),
                     ]
                     result = claw_mod._detect_openclaw_processes()
    ```

    ```diff
    @@ -736,8 +737,8 @@ def test_returns_match_on_windows_when_openclaw_exe_running(self):
         def test_returns_match_on_windows_when_node_exe_has_openclaw_in_cmdline(self):
             with patch.object(claw_mod, "sys") as mock_sys:
                 mock_sys.platform = "win32"
    -            with patch.object(claw_mod, "subprocess") as mock_subprocess:
    -                mock_subprocess.run.side_effect = [
    +            with patch("hermes_cli._subprocess_compat.run") as mock_run:
    +                mock_run.side_effect = [
                         MagicMock(returncode=0, stdout=""),  # tasklist openclaw.exe
                         MagicMock(returncode=0, stdout=""),  # tasklist clawd.exe
                         MagicMock(returncode=0, stdout="1234\n"),  # PowerShell
    ```

    ```diff
    @@ -749,8 +750,8 @@ def test_returns_match_on_windows_when_node_exe_has_openclaw_in_cmdline(self):
         def test_returns_empty_on_windows_when_nothing_found(self):
             with patch.object(claw_mod, "sys") as mock_sys:
                 mock_sys.platform = "win32"
    -            with patch.object(claw_mod, "subprocess") as mock_subprocess:
    -                mock_subprocess.run.side_effect = [
    +            with patch("hermes_cli._subprocess_compat.run") as mock_run:
    +                mock_run.side_effect = [
                         MagicMock(returncode=0, stdout=""),
                         MagicMock(returncode=0, stdout=""),
                         MagicMock(returncode=0, stdout=""),
    ```
  - `tests/hermes_cli/test_gateway.py`

    ```diff
    @@ -887,23 +887,20 @@ def test_reap_unsupervised_orphans_returns_false_when_none_found(monkeypatch):
     
     
     def test_scan_gateway_pids_detects_windows_hermes_exe_case_variants(monkeypatch):
    +    # Windows scan now goes through psutil first (no console spawn). A
    +    # uppercase ``Hermes.EXE gateway run`` must still match case-insensitively.
    +    import psutil
    +
         monkeypatch.setattr(gateway, "is_windows", lambda: True)
         monkeypatch.setattr(gateway, "_get_ancestor_pids", lambda: set())
    -    monkeypatch.setattr(gateway.shutil, "which", lambda name: "wmic.exe" if name == "wmic" else None)
    -
    -    def fake_run(cmd, **kwargs):
    -        if cmd[:4] == ["wmic.exe", "process", "get", "ProcessId,CommandLine"]:
    -            return SimpleNamespace(
    -                returncode=0,
    -                stdout=(
    -                    "CommandLine=C:\\Program Files\\Hermes\\Hermes.EXE gateway run --replace\n"
    -                    "ProcessId=2468\n\n"
    -                ),
    -                stderr="",
    -            )
    -        raise AssertionError(f"Unexpected command: {cmd}")
     
    -    monkeypatch.setattr(gateway.subprocess, "run", fake_run)
    +    proc = SimpleNamespace(
    +        info={
    +            "pid": 2468,
    +            "cmdline": ["C:\\Program Files\\Hermes\\Hermes.EXE", "gateway", "run", "--replace"],
    +        }
    +    )
    +    monkeypatch.setattr(psutil, "process_iter", lambda attrs=None: [proc])
     
         assert gateway._scan_gateway_pids(set(), all_profiles=True) == [2468]
     
    ```
  - `tests/hermes_cli/test_gateway_windows.py`

    ```diff
    @@ -29,6 +29,31 @@ def test_schtasks_fallback_does_not_hide_unknown_errors():
         assert gateway_windows._should_fall_back(1, "ERROR: The system cannot find the file specified.") is False
     
     
    +def test_noninteractive_stop_skips_schtasks_query(monkeypatch, tmp_path):
    +    """Desktop-triggered restarts must not invoke schtasks.exe.
    +
    +    schtasks is a console-subsystem binary; on Windows Terminal default hosts it
    +    can visibly pop a terminal even for `/Query`. Noninteractive desktop actions
    +    already stop the known gateway PID directly, so service-manager probing is
    +    unnecessary.
    +    """
    +
    +    script = tmp_path / "Hermes_Gateway.cmd"
    +    script.write_text("", encoding="utf-8")
    +
    +    monkeypatch.setenv("HERMES_NONINTERACTIVE", "1")
    +    monkeypatch.setattr(gateway_windows, "_assert_windows", lambda: None)
    +    monkeypatch.setattr(gateway_windows, "get_task_script_path", lambda: script)
    +    monkeypatch.setattr(gateway_windows, "get_startup_entry_path", lambda: tmp_path / "Hermes_Gateway.vbs")
    +    monkeypatch.setattr(gateway_windows, "_legacy_startup_entry_path", lambda: tmp_path / "Hermes_Gateway.cmd")
    +    monkeypatch.setattr(gateway_windows, "is_task_registered", lambda: pytest.fail("must not call schtasks"))
    +    monkeypatch.setattr("gateway.status.get_running_pid", lambda: None)
    +    monkeypatch.setattr(gateway_windows, "_collect_gateway_stop_pids", lambda pid=None: [])
    +    monkeypatch.setattr(gateway_windows, "_force_terminate_known_gateway_pids", lambda pids: 0)
    +
    +    gateway_windows.stop()
    +
    +
     def test_schtasks_encoding_falls_back_to_utf8(monkeypatch):
         """A broken/empty locale must not leave us without a decoder (issue #38172)."""
     
    ```

    ```diff
    @@ -112,6 +137,23 @@ def test_build_gateway_argv_uses_base_pythonw_for_uv_venv_launcher(monkeypatch,
         assert str(site_packages) in env_overlay["PYTHONPATH"].split(gateway_windows.os.pathsep)
     
     
    +def test_restart_relaunches_directly_without_start_service_probe(monkeypatch):
    +    calls = []
    +
    +    monkeypatch.setattr(gateway_windows, "_assert_windows", lambda: None)
    +    monkeypatch.setattr(gateway_windows, "stop", lambda: calls.append("stop"))
    +    monkeypatch.setattr(gateway_windows, "_wait_for_gateway_absent", lambda *a, **k: True)
    +    monkeypatch.setattr(gateway_windows.time, "sleep", lambda *_a, **_k: None)
    +    monkeypatch.setattr(gateway_windows, "_spawn_detached", lambda: 4321)
    +    monkeypatch.setattr(gateway_windows, "_report_gateway_start", lambda via: calls.append(("report", via)))
    +    monkeypatch.setattr(gateway_windows, "_wait_for_gateway_ready", lambda *a, **k: [4321])
    +    monkeypatch.setattr(gateway_windows, "start", lambda: pytest.fail("restart must not call start()"))
    +
    +    gateway_windows.restart()
    +
    +    assert calls == ["stop", ("report", "direct spawn (PID 4321)")]
    +
    +
     class TestStableWindowsGatewayWorkingDir:
         def test_stable_gateway_working_dir_uses_hermes_home(self, tmp_path, monkeypatch):
             home = tmp_path / ".hermes"
    ```
  - `tests/hermes_cli/test_web_server.py`

    ```diff
    @@ -215,6 +215,75 @@ def test_falls_back_to_random_token(self, monkeypatch):
             assert ws._SESSION_TOKEN and len(ws._SESSION_TOKEN) >= 32
     
     
    +class TestDashboardActionSpawnDetails:
    +    def test_windows_uses_uv_safe_pythonw_with_env_overlay(self, monkeypatch):
    +        import hermes_cli.web_server as ws
    +
    +        monkeypatch.setattr(ws.sys, "platform", "win32")
    +        monkeypatch.setattr(ws.sys, "executable", r"C:\venv\Scripts\python.exe")
    +        monkeypatch.setenv("PYTHONPATH", r"C:\existing")
    +        monkeypatch.setattr(
    +            "hermes_cli.gateway_windows._resolve_detached_python",
    +            lambda _exe: (
    +                r"C:\base\pythonw.exe",
    +                Path(r"C:\venv"),
    +                [r"C:\venv\Lib\site-packages"],
    +            ),
    +        )
    +
    +        executable, env_overlay = ws._dashboard_spawn_details()
    +
    +        assert executable == r"C:\base\pythonw.exe"
    +        assert env_overlay["VIRTUAL_ENV"] == str(Path(r"C:\venv"))
    +        assert env_overlay["PYTHONPATH"] == os.pathsep.join(
    +            [r"C:\venv\Lib\site-packages", r"C:\existing"]
    +        )
    +
    +    def test_windows_falls_back_to_sibling_pythonw_when_resolver_fails(self, monkeypatch):
    +        import hermes_cli.web_server as ws
    +
    +        exe = "C:/venv/Scripts/python.exe"
    +        expected = "C:/venv/Scripts/pythonw.exe"
    +
    +        monkeypatch.setattr(ws.sys, "platform", "win32")
    +        monkeypatch.setattr(ws.sys, "executable", exe)
    +        monkeypatch.setattr(
    +            "hermes_cli.gateway_windows._resolve_detached_python",
    +            lambda _exe: (_ for _ in ()).throw(RuntimeError("boom")),
    +        )
    +        monkeypatch.setattr(ws.os.path, "isfile", lambda candidate: candidate == expected)
    +
    +        executable, env_overlay = ws._dashboard_spawn_details()
    +
    +        assert executable == expected
    +        assert env_overlay == {}
    +
    +    def test_windows_resolves_venv_from_project_when_virtual_env_missing(self, monkeypatch, tmp_path):
    +        import hermes_cli.web_server as ws
    +
    +        project = tmp_path / "project"
    +        scripts = project / "venv" / "Scripts"
    +        site_packages = project / "venv" / "Lib" / "site-packages"
    +        scripts.mkdir(parents=True)
    +        site_packages.mkdir(parents=True)
    +        (scripts / "python.exe").write_text("", encoding="utf-8")
    +
    +        monkeypatch.delenv("VIRTUAL_ENV", raising=False)
    +        monkeypatch.setattr(ws.sys, "platform", "win32")
    +        monkeypatch.setattr(ws.sys, "executable", r"C:\base\pythonw.exe")
    +        monkeypatch.setattr(ws, "PROJECT_ROOT", project)
    +        monkeypatch.setattr(
    +            "hermes_cli.gateway_windows._resolve_detached_python",
    +            lambda exe: (r"C:\base\pythonw.exe", project / "venv", []),
    +        )
    +
    +        executable, env_overlay = ws._dashboard_spawn_details()
    +
    +        assert executable == r"C:\base\pythonw.exe"
    +        assert env_overlay["VIRTUAL_ENV"] == str(project / "venv")
    +        assert str(site_packages) in env_overlay["PYTHONPATH"].split(os.pathsep)
    +
    +
     # ---------------------------------------------------------------------------
     # web_server tests (FastAPI endpoints)
     # ---------------------------------------------------------------------------
    ```
  - `tests/test_hermes_bootstrap.py`

    ```diff
    @@ -21,6 +21,7 @@
     
     import io
     import os
    +import platform
     import subprocess
     import sys
     import textwrap
    ```

    ```diff
    @@ -231,6 +232,82 @@ def reconfigure(self, **kwargs):
             hb.apply_windows_utf8_bootstrap()
     
     
    +class TestWindowsPlatformProbeGuard:
    +    def test_windows_bootstrap_disables_platform_syscmd_subprocess(self):
    +        hb = _fresh_import()
    +        hb._IS_WINDOWS = True
    +        hb._bootstrap_applied = False
    +
    +        original = getattr(platform, "_syscmd_ver", None)
    +        try:
    +            hb.apply_windows_utf8_bootstrap()
    +
    +            assert platform._syscmd_ver("Windows", "", "") == ("Windows", "", "")
    +        finally:
    +            if original is not None:
    +                platform._syscmd_ver = original
    +
    +
    +class TestDetachOrphanConsole:
    +    """detach_orphan_console() frees a solo-owned console (the uv pythonw→python
    +    phantom) but leaves a shared interactive console attached, and is a pure
    +    no-op on POSIX. It is intentionally NOT run at import time."""
    +
    +    def test_noop_on_posix(self):
    +        hb = _fresh_import()
    +        hb._IS_WINDOWS = False
    +        assert hb.detach_orphan_console() is False
    +
    +    def test_not_called_at_import_time(self):
    +        # The FreeConsole catch-all must be opt-in per background entry point,
    +        # never an import side effect (would detach the interactive CLI/TUI).
    +        import pathlib
    +        src = pathlib.Path(_fresh_import().__file__).read_text(encoding="utf-8")
    +        body = src.split("def detach_orphan_console")[0]
    +        assert "FreeConsole" not in body, (
    +            "FreeConsole must live only inside detach_orphan_console(), not in "
    +            "apply_windows_utf8_bootstrap() / module import path"
    +        )
    +
    +    def _fake_ctypes(self, monkeypatch, window, nproc):
    +        import ctypes
    +
    +        class _K:
    +            def __init__(self):
    +                self.freed = False
    +            def GetConsoleWindow(self):
    +                return window
    +            def GetConsoleProcessList(self, buf, n):
    +                return nproc
    +            def FreeConsole(self):
    +                self.freed = True
    +
    +        k = _K()
    +        monkeypatch.setattr(ctypes, "windll", type("_W", (), {"kernel32": k})(), raising=False)
    +        return k
    +
    +    def test_frees_when_solo_owner(self, monkeypatch):
    +        hb = _fresh_import()
    +        hb._IS_WINDOWS = True
    +        k = self._fake_ctypes(monkeypatch, window=1, nproc=1)
    +        assert hb.detach_orphan_console() is True
    +        assert k.freed is True
    +
    +    def test_leaves_shared_console_attached(self, monkeypatch):
    +        hb = _fresh_import()
    +        hb._IS_WINDOWS = True
    +        k = self._fake_ctypes(monkeypatch, window=1, nproc=2)
    +        assert hb.detach_orphan_console() is False
    +        assert k.freed is False
    +
    +    def test_noop_without_console(self, monkeypatch):
    +        hb = _fresh_import()
    +        hb._IS_WINDOWS = True
    +        k = self._fake_ctypes(monkeypatch, window=0, nproc=1)
    +        assert hb.detach_orphan_console() is False
    +        assert k.freed is False
    +
    +
     class TestEntryPointsImportBootstrap:
         """Every Hermes entry point must import hermes_bootstrap as its
         first non-docstring import.  We check this by scanning source files
    ```
  - `tests/test_hermes_constants.py`

    ```diff
    @@ -1,6 +1,8 @@
     """Tests for hermes_constants module."""
     
     import os
    +import subprocess
    +import sys
     from pathlib import Path
     
     import pytest
    ```

    ```diff
    @@ -612,6 +614,30 @@ def test_npx_fallback_form_accepted(self):
             assert agent_browser_runnable("/usr/local/bin/npx agent-browser") is True
     
     
    +class TestAgentBrowserRunnableWindows:
    +    def test_windows_validation_hides_console_subprocess(self, tmp_path, monkeypatch):
    +        from hermes_cli import _subprocess_compat
    +
    +        exe = tmp_path / "agent-browser-win32-x64.exe"
    +        exe.write_text("", encoding="utf-8")
    +        exe.chmod(0o755)
    +        captured = {}
    +
    +        def fake_run(args, **kwargs):
    +            captured["args"] = args
    +            captured.update(kwargs)
    +            return type("Result", (), {"returncode": 0})()
    +
    +        monkeypatch.setattr(sys, "platform", "win32")
    +        monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", True)
    +        monkeypatch.setattr(subprocess, "run", fake_run)
    +
    +        assert agent_browser_runnable(str(exe)) is True
    +        assert captured["args"] == [str(exe), "--version"]
    +        assert "creationflags" in captured
    +        assert captured["stdin"] is subprocess.DEVNULL
    +
    +
     class TestGetHermesDir:
         """Tests for ``get_hermes_dir(new_subpath, old_name)``.
     
    ```
  - `tests/test_no_visible_console_spawns.py`

    ```diff
    @@ -0,0 +1,121 @@
    +"""Enforcement for the "no visible terminal on Windows" invariant.
    +
    +Windows console-subsystem programs (``taskkill``, ``schtasks``, ``agent-browser``,
    +``git-bash`` …) pop a console window unless spawned with ``CREATE_NO_WINDOW``.
    +Relying on each call site to remember the flag is how cron-driven and future
    +spawns leaked terminal windows. The durable fix is a single chokepoint —
    +``hermes_cli._subprocess_compat.run`` / ``.popen`` — that always injects the
    +flag on Windows, plus the ``FreeConsole`` catch-all in ``hermes_bootstrap`` for
    +Python children.
    +
    +These tests pin both halves of that contract:
    +
    +1. The primitive actually injects ``CREATE_NO_WINDOW`` (and merges, so detach
    +   callers still work).
    +2. No source file spawns a known console exe with a *raw* ``subprocess`` call,
    +   which would bypass the primitive and reintroduce the window.
    +"""
    +
    +from __future__ import annotations
    +
    +import re
    +from pathlib import Path
    +
    +import pytest
    +
    +from hermes_cli import _subprocess_compat
    +
    +REPO_ROOT = Path(__file__).resolve().parent.parent
    +_CREATE_NO_WINDOW = 0x08000000
    +
    +
    +class TestPrimitiveInjectsNoWindow:
    +    def test_run_injects_create_no_window_on_windows(self, monkeypatch):
    +        captured: dict = {}
    +        monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", True)
    +        monkeypatch.setattr(
    +            _subprocess_compat.subprocess, "run", lambda cmd, **kw: captured.update(kw) or "ok"
    +        )
    +
    +        _subprocess_compat.run(["taskkill"], timeout=5)
    +
    +        assert captured["creationflags"] & _CREATE_NO_WINDOW
    +
    +    def test_popen_injects_create_no_window_on_windows(self, monkeypatch):
    +        captured: dict = {}
    +        monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", True)
    +        monkeypatch.setattr(
    +            _subprocess_compat.subprocess, "Popen", lambda cmd, **kw: captured.update(kw) or "ok"
    +        )
    +
    +        _subprocess_compat.popen(["agent-browser"])
    +
    +        assert captured["creationflags"] & _CREATE_NO_WINDOW
    +
    +    def test_merges_with_existing_detach_flags(self, monkeypatch):
    +        captured: dict = {}
    +        monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", True)
    +        monkeypatch.setattr(
    +            _subprocess_compat.subprocess, "run", lambda cmd, **kw: captured.update(kw) or "ok"
    +        )
    +
    +        detach = _subprocess_compat.windows_detach_flags()
    +        _subprocess_compat.run(["x"], creationflags=detach)
    +
    +        assert captured["creationflags"] & _CREATE_NO_WINDOW
    +        assert captured["creationflags"] & detach == detach
    +
    +    def test_no_op_on_posix(self, monkeypatch):
    +        captured: dict = {}
    +        monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", False)
    +        monkeypatch.setattr(
    +            _subprocess_compat.subprocess, "run", lambda cmd, **kw: captured.update(kw) or "ok"
    +        )
    +
    +        _subprocess_compat.run(["x"])
    +
    +        assert "creationflags" not in captured
    +
    +
    +# Windows-only console tools — they have no POSIX use, so a raw ``subprocess``
    +# spawn is unambiguously a Windows path that flashes a terminal. Banning them
    +# repo-wide is a pure win (cross-platform tools like git/ffmpeg/node are NOT
    +# listed: they have legitimate foreground/POSIX uses a blanket ban would break;
    +# their Windows-background call sites are routed through the primitive instead).
    +# ``_subprocess_compat.run/.popen`` calls never match these (different prefix).
    +_WINDOWS_ONLY_CONSOLE_EXES = ("taskkill", "schtasks", "wmic", "netstat", "tasklist")
    +_RAW_CONSOLE_SPAWNS = [
    +    re.compile(rf"""subprocess\.(?:run|Popen|call)\(\s*\[\s*["']{exe}["']""")
    +    for exe in _WINDOWS_ONLY_CONSOLE_EXES
    +]
    +
    +# The primitive itself is allowed to call raw subprocess — it IS the chokepoint.
    +_ALLOWED = {REPO_ROOT / "hermes_cli" / "_subprocess_compat.py"}
    +
    +
    +# Dev/CI tooling that never ships to a user's Windows desktop, where a flashing
    +# console is irrelevant and importing hermes_cli would be inappropriate.
    +_SKIP_DIRS = {"tests", "node_modules", ".venv", "venv", "scripts"}
    +
    +
    +def _python_sources():
    +    for path in REPO_ROOT.rglob("*.py"):
    +        if _SKIP_DIRS & set(path.parts):
    +            continue
    +        if path in _ALLOWED:
    +            continue
    +        yield path
    +
    +
    [redacted:email]("pattern", _RAW_CONSOLE_SPAWNS, ids=_WINDOWS_ONLY_CONSOLE_EXES)
    +def test_no_raw_console_exe_spawns(pattern):
    +    offenders = [
    +        str(path.relative_to(REPO_ROOT))
    +        for path in _python_sources()
    +        if pattern.search(path.read_text(encoding="utf-8", errors="ignore"))
    +    ]
    +
    +    assert not offenders, (
    +        "Console-subsystem exe spawned via raw subprocess (flashes a terminal on "
    +        f"Windows). Route through hermes_cli._subprocess_compat.run/.popen instead: {offenders}"
    +    )
    ```
  - `tests/tools/test_browser_hardening.py`

    ```diff
    @@ -89,6 +89,28 @@ def mock_exists(self):
             with pytest.raises(FileNotFoundError, match="cached"):
                 bt._find_agent_browser()
     
    +    def test_windows_prefers_native_agent_browser_exe_over_cmd_shim(self, tmp_path, monkeypatch):
    +        import tools.browser_tool as bt
    +
    +        repo = tmp_path / "repo"
    +        native = repo / "node_modules" / "agent-browser" / "bin" / "agent-browser-win32-x64.exe"
    +        cmd = repo / "node_modules" / ".bin" / "agent-browser.cmd"
    +        native.parent.mkdir(parents=True)
    +        cmd.parent.mkdir(parents=True)
    +        native.write_text("", encoding="utf-8")
    +        cmd.write_text("", encoding="utf-8")
    +
    +        def fake_which(command, path=None):
    +            return str(cmd) if path == str(cmd.parent) else None
    +
    +        monkeypatch.setattr(bt.sys, "platform", "win32")
    +        monkeypatch.setattr(bt.shutil, "which", fake_which)
    +        monkeypatch.setattr(bt, "agent_browser_runnable", lambda path: True)
    +        monkeypatch.setattr(bt, "_merge_browser_path", lambda path: "")
    +        monkeypatch.setattr(bt, "__file__", str(repo / "tools" / "browser_tool.py"))
    +
    +        assert bt._find_agent_browser() == str(native)
    +
     
     # ---------------------------------------------------------------------------
     # Caching: _get_command_timeout
    ```
  - `tests/tools/test_windows_native_support.py`

    ```diff
    @@ -188,11 +188,13 @@ class TestTerminatePidRoutingOnWindows:
     
         def test_force_uses_taskkill_on_windows(self, monkeypatch):
             from gateway import status
    +        from hermes_cli import _subprocess_compat
     
             captured = {}
     
             def fake_run(args, **kwargs):
                 captured["args"] = args
    +            captured.update(kwargs)
                 result = MagicMock()
                 result.returncode = 0
                 result.stderr = ""
    ```

    ```diff
    @@ -200,6 +202,7 @@ def fake_run(args, **kwargs):
                 return result
     
             monkeypatch.setattr(status, "_IS_WINDOWS", True)
    +        monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", True)
             monkeypatch.setattr(status.subprocess, "run", fake_run)
             status.terminate_pid(12345, force=True)
     
    ```

    ```diff
    @@ -208,6 +211,7 @@ def fake_run(args, **kwargs):
             assert "12345" in captured["args"]
             assert "/T" in captured["args"]
             assert "/F" in captured["args"]
    +        assert captured["creationflags"] & 0x08000000
     
         def test_force_taskkill_failure_raises_oserror(self, monkeypatch):
             from gateway import status
    ```
  - `tools/browser_tool.py`

    ```diff
    @@ -68,7 +68,7 @@
     from hermes_constants import agent_browser_runnable, get_hermes_home
     from utils import env_int, is_truthy_value
     from hermes_cli.config import DEFAULT_CONFIG, cfg_get
    -from hermes_cli._subprocess_compat import windows_hide_flags
    +from hermes_cli import _subprocess_compat
     
     try:
         from tools.website_policy import check_website_access
    ```

    ```diff
    @@ -905,23 +905,19 @@ def _run_tmp(cmd: str, cmd_args: List[str]) -> Dict[str, Any]:
                 #   fileno=1 (stderr dup'd onto stdout at the OS level).
                 # * close_fds=True → block inheritance of every other handle.
                 #   (Default on POSIX; must be explicit on Windows for stdio.)
    +            # CREATE_NO_WINDOW is applied by _subprocess_compat.popen. We do NOT
    +            # add CREATE_NEW_PROCESS_GROUP: on Python 3.11 Windows it interacts
    +            # with asyncio's ProactorEventLoop such that the subprocess creation
    +            # cancels the running loop task, surfacing as KeyboardInterrupt in
    +            # app.run() and tearing down the CLI mid-turn (diag:
    +            # "asyncio.CancelledError → KeyboardInterrupt").
                 _popen_extra: dict = {}
                 if os.name == "nt":
    -                # CREATE_NO_WINDOW → don't attach a console (cmd.exe would
    -                # otherwise briefly allocate one for the .cmd shim).
    -                # Do NOT add CREATE_NEW_PROCESS_GROUP: on Python 3.11 Windows
    -                # it interacts with asyncio's ProactorEventLoop such that the
    -                # subprocess creation cancels the running loop task, which
    -                # surfaces as KeyboardInterrupt in app.run() and tears down
    -                # the CLI mid-turn. The agent thread's subprocess spawn
    -                # unwound MainThread's prompt_toolkit loop that way — see
    -                # diag log: "asyncio.CancelledError → KeyboardInterrupt".
    -                _popen_extra["creationflags"] = windows_hide_flags()
                     _popen_extra["close_fds"] = True
                     _si = subprocess.STARTUPINFO()
                     _si.dwFlags |= subprocess.STARTF_USESTDHANDLES
                     _popen_extra["startupinfo"] = _si
    -            proc = subprocess.Popen(
    +            proc = _subprocess_compat.popen(
                     full, stdout=stdout_fd, stderr=stderr_fd,
                     stdin=subprocess.DEVNULL, env=browser_env,
                     **_popen_extra,
    ```

    ```diff
    @@ -1942,6 +1938,12 @@ def _find_agent_browser() -> str:
         repo_root = Path(__file__).parent.parent
         local_bin_dir = repo_root / "node_modules" / ".bin"
         if local_bin_dir.is_dir():
    +        if sys.platform == "win32":
    +            native = repo_root / "node_modules" / "agent-browser" / "bin" / "agent-browser-win32-x64.exe"
    +            if native.exists() and agent_browser_runnable(str(native)):
    +                _cached_agent_browser = str(native)
    +                _agent_browser_resolved = True
    +                return _cached_agent_browser
             local_which = shutil.which("agent-browser", path=str(local_bin_dir))
             if local_which and agent_browser_runnable(local_which):
                 _cached_agent_browser = local_which
    ```

    ```diff
    @@ -2191,17 +2193,16 @@ def _run_browser_command(
                 # three explicit handles (no leaked parent-console handles to
                 # confuse the Rust binary's daemon-spawn), and close_fds=True to
                 # block inheritance of everything else.
    +            # CREATE_NO_WINDOW via _subprocess_compat.popen; NO
    +            # CREATE_NEW_PROCESS_GROUP (cancels asyncio loop task on Python 3.11
    +            # Windows → KeyboardInterrupt in CLI MainThread).
                 _popen_extra: dict = {}
                 if os.name == "nt":
    -                # See matching block at the other Popen site — CREATE_NO_WINDOW
    -                # only, NO CREATE_NEW_PROCESS_GROUP (cancels asyncio loop task
    -                # on Python 3.11 Windows → KeyboardInterrupt in CLI MainThread).
    -                _popen_extra["creationflags"] = windows_hide_flags()
                     _popen_extra["close_fds"] = True
                     _si = subprocess.STARTUPINFO()
                     _si.dwFlags |= subprocess.STARTF_USESTDHANDLES
                     _popen_extra["startupinfo"] = _si
    -            proc = subprocess.Popen(
    +            proc = _subprocess_compat.popen(
                     cmd_parts,
                     stdout=stdout_fd,
                     stderr=stderr_fd,
    ```
  - `tools/environments/local.py`

    ```diff
    @@ -13,7 +13,7 @@
     from pathlib import Path
     
     from tools.environments.base import BaseEnvironment, _pipe_stdin
    -from hermes_cli._subprocess_compat import windows_hide_flags
    +from hermes_cli import _subprocess_compat
     
     _IS_WINDOWS = platform.system() == "Windows"
     
    ```

    ```diff
    @@ -738,9 +738,7 @@ def _run_bash(self, cmd_string: str, *, login: bool = False,
     
             _popen_cwd = self.cwd
     
    -        _popen_kwargs = {"creationflags": windows_hide_flags()} if _IS_WINDOWS else {}
    -
    -        proc = subprocess.Popen(
    +        proc = _subprocess_compat.popen(
                 args,
                 text=True,
                 env=run_env,
    ```

    ```diff
    @@ -751,7 +749,6 @@ def _run_bash(self, cmd_string: str, *, login: bool = False,
                 stdin=subprocess.PIPE if stdin_data is not None else subprocess.DEVNULL,
                 start_new_session=True,
                 cwd=_popen_cwd,
    -            **_popen_kwargs,
             )
             if not _IS_WINDOWS:
                 try:
    ```
  - `tools/process_registry.py`

    ```diff
    @@ -42,6 +42,7 @@
     
     _IS_WINDOWS = platform.system() == "Windows"
     from tools.environments.local import _find_shell, _resolve_safe_cwd, _sanitize_subprocess_env
    +from hermes_cli import _subprocess_compat
     from hermes_cli._subprocess_compat import windows_hide_flags
     from dataclasses import dataclass, field
     from typing import Any, Dict, List, Optional
    ```

    ```diff
    @@ -569,12 +570,11 @@ def _terminate_host_pid(cls, pid: int, expected_start: Optional[int] = None) ->
                 return
             if _IS_WINDOWS:
                 try:
    -                subprocess.run(
    +                _subprocess_compat.run(
                         ["taskkill", "/PID", str(pid), "/T", "/F"],
                         capture_output=True,
                         text=True,
                         timeout=10,
    -                    creationflags=windows_hide_flags(),
                         stdin=subprocess.DEVNULL,
                     )
                 except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
    ```
  - `tools/transcription_tools.py`

    ```diff
    @@ -37,6 +37,7 @@
     from typing import Optional, Dict, Any
     from urllib.parse import urljoin
     
    +from hermes_cli import _subprocess_compat
     from utils import is_truthy_value
     from tools.managed_tool_gateway import resolve_managed_tool_gateway
     from tools.tool_backend_helpers import (
    ```

    ```diff
    @@ -485,7 +486,7 @@ def _terminate_command_stt_process_tree(proc: subprocess.Popen) -> None:
     
         if os.name == "nt":
             try:
    -            subprocess.run(
    +            _subprocess_compat.run(
                     ["taskkill", "/F", "/T", "/PID", str(proc.pid)],
                     stdout=subprocess.DEVNULL,
                     stderr=subprocess.DEVNULL,
    ```

    ```diff
    @@ -556,7 +557,7 @@ def _run_command_stt(command: str, timeout: float) -> subprocess.CompletedProces
         else:
             popen_kwargs["start_new_session"] = True
     
    -    proc = subprocess.Popen(command, **popen_kwargs, stdin=subprocess.DEVNULL)
    +    proc = _subprocess_compat.popen(command, **popen_kwargs, stdin=subprocess.DEVNULL)
         try:
             stdout, stderr = proc.communicate(timeout=timeout)
         except subprocess.TimeoutExpired as exc:
    ```

    ```diff
    @@ -1187,7 +1188,7 @@ def _prepare_local_audio(file_path: str, work_dir: str) -> tuple[Optional[str],
         command = [ffmpeg, "-y", "-i", file_path, converted_path]
     
         try:
    -        subprocess.run(command, check=True, capture_output=True, text=True, timeout=300, stdin=subprocess.DEVNULL)
    +        _subprocess_compat.run(command, check=True, capture_output=True, text=True, timeout=300, stdin=subprocess.DEVNULL)
             return converted_path, None
         except subprocess.TimeoutExpired:
             logger.error("ffmpeg conversion timed out for %s", file_path)
    ```

    ```diff
    @@ -1233,9 +1234,9 @@ def _transcribe_local_command(file_path: str, model_name: str) -> Dict[str, Any]
                 # User-provided templates (env var) may contain shell syntax; auto-detected commands are safe for list mode.
                 use_shell = bool(os.getenv(LOCAL_STT_COMMAND_ENV, "").strip())
                 if use_shell:
    -                subprocess.run(command, shell=True, check=True, capture_output=True, text=True, timeout=300, stdin=subprocess.DEVNULL)
    +                _subprocess_compat.run(command, shell=True, check=True, capture_output=True, text=True, timeout=300, stdin=subprocess.DEVNULL)
                 else:
    -                subprocess.run(shlex.split(command), check=True, capture_output=True, text=True, timeout=300, stdin=subprocess.DEVNULL)
    +                _subprocess_compat.run(shlex.split(command), check=True, capture_output=True, text=True, timeout=300, stdin=subprocess.DEVNULL)
                 
     
                 txt_files = sorted(Path(output_dir).glob("*.txt"))
    ```
  - `tools/tts_tool.py`

    ```diff
    @@ -52,6 +52,7 @@
     from typing import Callable, Dict, Any, Optional
     from urllib.parse import urljoin
     
    +from hermes_cli import _subprocess_compat
     from hermes_constants import display_hermes_home
     
     logger = logging.getLogger(__name__)
    ```

    ```diff
    @@ -714,7 +715,7 @@ def _terminate_command_tts_process_tree(proc: subprocess.Popen) -> None:
     
         if os.name == "nt":
             try:
    -            subprocess.run(
    +            _subprocess_compat.run(
                     ["taskkill", "/F", "/T", "/PID", str(proc.pid)],
                     stdout=subprocess.DEVNULL,
                     stderr=subprocess.DEVNULL,
    ```

    ```diff
    @@ -772,7 +773,7 @@ def _run_command_tts(command: str, timeout: float) -> subprocess.CompletedProces
         else:
             popen_kwargs["start_new_session"] = True
     
    -    proc = subprocess.Popen(command, **popen_kwargs, stdin=subprocess.DEVNULL)
    +    proc = _subprocess_compat.popen(command, **popen_kwargs, stdin=subprocess.DEVNULL)
         try:
             stdout, stderr = proc.communicate(timeout=timeout)
         except subprocess.TimeoutExpired as exc:
    ```

    ```diff
    @@ -905,7 +906,7 @@ def _convert_to_opus(mp3_path: str) -> Optional[str]:
     
         ogg_path = mp3_path.rsplit(".", 1)[0] + ".ogg"
         try:
    -        result = subprocess.run(
    +        result = _subprocess_compat.run(
                 ["ffmpeg", "-i", mp3_path, "-acodec", "libopus",
                  "-ac", "1", "-b:a", "64k", "-vbr", "off", ogg_path, "-y"],
                 capture_output=True, timeout=30,
    ```

    ```diff
    @@ -1776,7 +1777,7 @@ def _generate_gemini_tts(text: str, output_path: str, tts_config: Dict[str, Any]
                     ]
                 else:
                     cmd = [ffmpeg, "-i", wav_path, "-y", "-loglevel", "error", output_path]
    -            result = subprocess.run(cmd, capture_output=True, timeout=30, stdin=subprocess.DEVNULL)
    +            result = _subprocess_compat.run(cmd, capture_output=True, timeout=30, stdin=subprocess.DEVNULL)
                 if result.returncode != 0:
                     stderr = result.stderr.decode("utf-8", errors="ignore")[:300]
                     raise RuntimeError(f"ffmpeg conversion failed: {stderr}")
    ```

    ```diff
    @@ -1859,7 +1860,7 @@ def _generate_neutts(text: str, output_path: str, tts_config: Dict[str, Any]) ->
             "--device", device,
         ]
     
    -    result = subprocess.run(cmd, capture_output=True, text=True, timeout=120, stdin=subprocess.DEVNULL)
    +    result = _subprocess_compat.run(cmd, capture_output=True, text=True, timeout=120, stdin=subprocess.DEVNULL)
         if result.returncode != 0:
             stderr = result.stderr.strip()
             # Filter out the "OK:" line from stderr
    ```

    ```diff
    @@ -1871,7 +1872,7 @@ def _generate_neutts(text: str, output_path: str, tts_config: Dict[str, Any]) ->
             ffmpeg = shutil.which("ffmpeg")
             if ffmpeg:
                 conv_cmd = [ffmpeg, "-i", wav_path, "-y", "-loglevel", "error", output_path]
    -            subprocess.run(conv_cmd, check=True, timeout=30, stdin=subprocess.DEVNULL, creationflags=windows_hide_flags())
    +            _subprocess_compat.run(conv_cmd, check=True, timeout=30, stdin=subprocess.DEVNULL)
                 os.remove(wav_path)
             else:
                 # No ffmpeg — just rename the WAV to the expected path
    ```

    ```diff
    @@ -1938,7 +1939,7 @@ def _resolve_piper_voice_path(voice: str, download_dir: Path) -> str:
         import sys as _sys
         logger.info("[Piper] Downloading voice '%s' to %s (first use)", voice, download_dir)
         try:
    -        result = subprocess.run(
    +        result = _subprocess_compat.run(
                 [_sys.executable, "-m", "piper.download_voices", voice,
                  "--download-dir", str(download_dir)],
                 capture_output=True, text=True, timeout=300,
    ```

    ```diff
    @@ -2050,7 +2051,7 @@ def _generate_piper_tts(text: str, output_path: str, tts_config: Dict[str, Any])
             ffmpeg = shutil.which("ffmpeg")
             if ffmpeg:
                 conv_cmd = [ffmpeg, "-i", wav_path, "-y", "-loglevel", "error", output_path]
    -            subprocess.run(conv_cmd, check=True, timeout=30, stdin=subprocess.DEVNULL, creationflags=windows_hide_flags())
    +            _subprocess_compat.run(conv_cmd, check=True, timeout=30, stdin=subprocess.DEVNULL)
                 try:
                     os.remove(wav_path)
                 except OSError:
    ```

    ```diff
    @@ -2116,7 +2117,7 @@ def _generate_kittentts(text: str, output_path: str, tts_config: Dict[str, Any])
             ffmpeg = shutil.which("ffmpeg")
             if ffmpeg:
                 conv_cmd = [ffmpeg, "-i", wav_path, "-y", "-loglevel", "error", output_path]
    -            subprocess.run(conv_cmd, check=True, timeout=30, stdin=subprocess.DEVNULL, creationflags=windows_hide_flags())
    +            _subprocess_compat.run(conv_cmd, check=True, timeout=30, stdin=subprocess.DEVNULL)
                 os.remove(wav_path)
             else:
                 # No ffmpeg — rename the WAV to the expected path
    ```

    ```diff
    @@ -2812,7 +2813,6 @@ def _check(importer, label):
     # Registry
     # ---------------------------------------------------------------------------
     from tools.registry import registry, tool_error
    -from hermes_cli._subprocess_compat import windows_hide_flags
     
     TTS_SCHEMA = {
         "name": "text_to_speech",
    ```
  - `tui_gateway/entry.py`

    ```diff
    @@ -260,6 +260,13 @@ def join_mcp_discovery(timeout: float | None = None) -> bool:
     
     
     def main():
    +    # Stdio backend spawned by Node/Electron: drop any console a uv
    +    # pythonw→python re-exec auto-allocated. No-op on POSIX.
    +    try:
    +        hermes_bootstrap.detach_orphan_console()
    +    except Exception:
    +        pass
    +
         _install_sidecar_publisher()
     
         # MCP tool discovery — runs in a background daemon thread so a slow or
    ```
  - `tui_gateway/git_probe.py`

    ```diff
    @@ -46,7 +46,9 @@ def run_git(cwd: str, *args: str) -> str:
         if not cwd:
             return ""
         try:
    -        result = subprocess.run(
    +        from hermes_cli import _subprocess_compat
    +
    +        result = _subprocess_compat.run(
                 ["git", "-C", cwd, *args],
                 capture_output=True,
                 text=True,
    ```
  - `tui_gateway/slash_worker.py`

    ```diff
    @@ -93,6 +93,14 @@ def _run(cli: HermesCLI, command: str) -> str:
     
     
     def main():
    +    # Stdio worker spawned by the gateway: drop any console a uv pythonw→python
    +    # re-exec auto-allocated. No-op on POSIX.
    +    try:
    +        import hermes_bootstrap
    +        hermes_bootstrap.detach_orphan_console()
    +    except Exception:
    +        pass
    +
         p = argparse.ArgumentParser(add_help=False)
         p.add_argument("--session-key", required=True)
         p.add_argument("--model", default="")
    ```
- **#53892: fix(windows): hide backend console-flash legs main missed (Electron base-pythonw, ffmpeg, netstat)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `apps/desktop/electron/main.cjs`, `apps/desktop/electron/windows-child-process.test.cjs`, `plugins/platforms/discord/adapter.py`, `plugins/platforms/whatsapp/adapter.py`, `tests/test_windows_subprocess_no_window_flags.py`, `tools/transcription_tools.py`, `tools/tts_tool.py`
  - fix hunks:
  - `apps/desktop/electron/main.cjs`

    ```diff
    @@ -1572,19 +1572,17 @@ function readVenvHome(venvRoot) {
     function getNoConsoleVenvPython(venvRoot) {
       if (!IS_WINDOWS) return getVenvPython(venvRoot)
     
    -  // Prefer the venv's own pythonw shim — it carries pyvenv.cfg / site-packages
    -  // wiring. Falling back to the base uv/python.org pythonw.exe skips the venv
    -  // and breaks imports (yaml, hermes_cli, …) even when PYTHONPATH is patched.
    -  const venvPythonw = path.join(venvRoot, 'Scripts', 'pythonw.exe')
    -  if (fileExists(venvPythonw)) return venvPythonw
    -
    +  // The venv's ``Scripts\pythonw.exe`` is a uv launcher shim that re-execs the
    +  // base console ``python.exe``, allocating a conhost/Windows Terminal window
    +  // that CREATE_NO_WINDOW can't suppress. Use the base ``pythonw.exe`` directly;
    +  // callers put the venv site-packages on PYTHONPATH so imports still resolve.
       const baseHome = readVenvHome(venvRoot)
       if (baseHome) {
         const basePythonw = path.join(baseHome, 'pythonw.exe')
         if (fileExists(basePythonw)) return basePythonw
       }
     
    -  return venvPythonw
    +  return path.join(venvRoot, 'Scripts', 'pythonw.exe')
     }
     
     function toNoConsolePython(pythonPath) {
    ```

    ```diff
    @@ -2847,7 +2845,7 @@ function createPythonBackend(root, label, dashboardArgs, options = {}) {
         args: ['-m', 'hermes_cli.main', ...dashboardArgs],
         env: buildDesktopBackendEnv({
           hermesHome: HERMES_HOME,
    -      pythonPathEntries: [root],
    +      pythonPathEntries: [root, ...getVenvSitePackagesEntries(venvRoot)],
           venvRoot
         }),
         root,
    ```

    ```diff
    @@ -2871,7 +2869,7 @@ function createActiveBackend(dashboardArgs) {
         args: ['-m', 'hermes_cli.main', ...dashboardArgs],
         env: buildDesktopBackendEnv({
           hermesHome: HERMES_HOME,
    -      pythonPathEntries: [ACTIVE_HERMES_ROOT],
    +      pythonPathEntries: [ACTIVE_HERMES_ROOT, ...getVenvSitePackagesEntries(VENV_ROOT)],
           venvRoot: VENV_ROOT
         }),
         root: ACTIVE_HERMES_ROOT,
    ```
  - `apps/desktop/electron/windows-child-process.test.cjs`

    ```diff
    @@ -53,6 +53,22 @@ test('desktop background child processes opt into hidden Windows consoles', () =
       assert.match(source, /args: \['-m', 'hermes_cli\.main', \.\.\.dashboardArgs\]/)
     })
     
    +test('getNoConsoleVenvPython prefers base pythonw over the uv re-exec shim', () => {
    +  const source = readElectronFile('main.cjs')
    +  const body = source.slice(
    +    source.indexOf('function getNoConsoleVenvPython(venvRoot)'),
    +    source.indexOf('function getVenvSitePackagesEntries(venvRoot)')
    +  )
    +
    +  // The venv Scripts\pythonw.exe re-execs a console python.exe (flashes a
    +  // conhost); the base pythonw must be resolved first so it never runs.
    +  const baseIdx = body.indexOf('basePythonw')
    +  const shimIdx = body.indexOf("'Scripts', 'pythonw.exe'")
    +  assert.notEqual(baseIdx, -1, 'base pythonw resolution missing')
    +  assert.notEqual(shimIdx, -1, 'venv shim fallback missing')
    +  assert.ok(baseIdx < shimIdx, 'base pythonw must be preferred before the venv Scripts shim')
    +})
    +
     test('intentional or interactive desktop child processes stay documented', () => {
       const source = readElectronFile('main.cjs')
     
    ```
  - `plugins/platforms/discord/adapter.py`

    ```diff
    @@ -665,6 +665,8 @@ def pcm_to_wav(pcm_data: bytes, output_path: str,
                 f.write(pcm_data)
                 pcm_path = f.name
             try:
    +            from hermes_cli._subprocess_compat import windows_hide_flags
    +
                 subprocess.run(
                     [
                         "ffmpeg", "-y", "-loglevel", "error",
    ```

    ```diff
    @@ -679,6 +681,7 @@ def pcm_to_wav(pcm_data: bytes, output_path: str,
                     check=True,
                     timeout=10,
                     stdin=subprocess.DEVNULL,
    +                creationflags=windows_hide_flags(),
                 )
             finally:
                 try:
    ```
  - `plugins/platforms/whatsapp/adapter.py`

    ```diff
    @@ -78,10 +78,13 @@ def _kill_port_process(port: int) -> None:
         """Kill any process *listening* on the given TCP port (a stale bridge)."""
         try:
             if _IS_WINDOWS:
    +            from hermes_cli._subprocess_compat import windows_hide_flags
    +
                 # Use netstat to find the PID bound to this port, then taskkill
                 result = subprocess.run(
                     ["netstat", "-ano", "-p", "TCP"],
                     capture_output=True, text=True, timeout=5,
    +                creationflags=windows_hide_flags(),
                 )
                 for line in result.stdout.splitlines():
                     parts = line.split()
    ```

    ```diff
    @@ -92,6 +95,7 @@ def _kill_port_process(port: int) -> None:
                                 subprocess.run(
                                     ["taskkill", "/PID", parts[4], "/F"],
                                     capture_output=True, timeout=5,
    +                                creationflags=windows_hide_flags(),
                                 )
                             except subprocess.SubprocessError:
                                 pass
    ```
  - `tests/test_windows_subprocess_no_window_flags.py`

    ```diff
    @@ -261,3 +261,41 @@ def fake_run(cmd, **kwargs):
         assert skill_preprocessing.run_inline_shell("echo ok", cwd=None, timeout=5) == "ok"
         assert captured[0][0] == ["bash", "-c", "echo ok"]
         assert captured[0][1]["creationflags"] == _CREATE_NO_WINDOW
    +
    +
    +def test_tts_opus_conversion_hides_ffmpeg_window(monkeypatch, tmp_path):
    +    from tools import tts_tool
    +
    +    captured = []
    +
    +    def fake_run(cmd, **kwargs):
    +        captured.append((cmd, kwargs))
    +        return _Completed(returncode=0)
    +
    +    monkeypatch.setattr(tts_tool, "_has_ffmpeg", lambda: True)
    +    monkeypatch.setattr(tts_tool, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
    +    monkeypatch.setattr(tts_tool.subprocess, "run", fake_run)
    +
    +    tts_tool._convert_to_opus(str(tmp_path / "v.mp3"))
    +
    +    assert captured[0][0][0] == "ffmpeg"
    +    assert captured[0][1]["creationflags"] == _CREATE_NO_WINDOW
    +
    +
    +def test_local_stt_audio_prep_hides_ffmpeg_window(monkeypatch, tmp_path):
    +    from tools import transcription_tools
    +
    +    captured = []
    +
    +    def fake_run(cmd, **kwargs):
    +        captured.append((cmd, kwargs))
    +        return _Completed(returncode=0)
    +
    +    monkeypatch.setattr(transcription_tools, "_find_ffmpeg_binary", lambda: "ffmpeg")
    +    monkeypatch.setattr(transcription_tools, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
    +    monkeypatch.setattr(transcription_tools.subprocess, "run", fake_run)
    +
    +    transcription_tools._prepare_local_audio(str(tmp_path / "in.m4a"), str(tmp_path))
    +
    +    assert captured[0][0][0] == "ffmpeg"
    +    assert captured[0][1]["creationflags"] == _CREATE_NO_WINDOW
    ```
  - `tools/transcription_tools.py`

    ```diff
    @@ -37,6 +37,7 @@
     from typing import Optional, Dict, Any
     from urllib.parse import urljoin
     
    +from hermes_cli._subprocess_compat import windows_hide_flags
     from utils import is_truthy_value
     from tools.managed_tool_gateway import resolve_managed_tool_gateway
     from tools.tool_backend_helpers import (
    ```

    ```diff
    @@ -1187,7 +1188,7 @@ def _prepare_local_audio(file_path: str, work_dir: str) -> tuple[Optional[str],
         command = [ffmpeg, "-y", "-i", file_path, converted_path]
     
         try:
    -        subprocess.run(command, check=True, capture_output=True, text=True, timeout=300, stdin=subprocess.DEVNULL)
    +        subprocess.run(command, check=True, capture_output=True, text=True, timeout=300, stdin=subprocess.DEVNULL, creationflags=windows_hide_flags())
             return converted_path, None
         except subprocess.TimeoutExpired:
             logger.error("ffmpeg conversion timed out for %s", file_path)
    ```

    ```diff
    @@ -1233,9 +1234,9 @@ def _transcribe_local_command(file_path: str, model_name: str) -> Dict[str, Any]
                 # User-provided templates (env var) may contain shell syntax; auto-detected commands are safe for list mode.
                 use_shell = bool(os.getenv(LOCAL_STT_COMMAND_ENV, "").strip())
                 if use_shell:
    -                subprocess.run(command, shell=True, check=True, capture_output=True, text=True, timeout=300, stdin=subprocess.DEVNULL)
    +                subprocess.run(command, shell=True, check=True, capture_output=True, text=True, timeout=300, stdin=subprocess.DEVNULL, creationflags=windows_hide_flags())
                 else:
    -                subprocess.run(shlex.split(command), check=True, capture_output=True, text=True, timeout=300, stdin=subprocess.DEVNULL)
    +                subprocess.run(shlex.split(command), check=True, capture_output=True, text=True, timeout=300, stdin=subprocess.DEVNULL, creationflags=windows_hide_flags())
                 
     
                 txt_files = sorted(Path(output_dir).glob("*.txt"))
    ```
  - `tools/tts_tool.py`

    ```diff
    @@ -52,6 +52,7 @@
     from typing import Callable, Dict, Any, Optional
     from urllib.parse import urljoin
     
    +from hermes_cli._subprocess_compat import windows_hide_flags
     from hermes_constants import display_hermes_home
     
     logger = logging.getLogger(__name__)
    ```

    ```diff
    @@ -910,6 +911,7 @@ def _convert_to_opus(mp3_path: str) -> Optional[str]:
                  "-ac", "1", "-b:a", "64k", "-vbr", "off", ogg_path, "-y"],
                 capture_output=True, timeout=30,
                 stdin=subprocess.DEVNULL,
    +            creationflags=windows_hide_flags(),
             )
             if result.returncode != 0:
                 logger.warning("ffmpeg conversion failed with return code %d: %s", 
    ```

    ```diff
    @@ -1776,7 +1778,7 @@ def _generate_gemini_tts(text: str, output_path: str, tts_config: Dict[str, Any]
                     ]
                 else:
                     cmd = [ffmpeg, "-i", wav_path, "-y", "-loglevel", "error", output_path]
    -            result = subprocess.run(cmd, capture_output=True, timeout=30, stdin=subprocess.DEVNULL)
    +            result = subprocess.run(cmd, capture_output=True, timeout=30, stdin=subprocess.DEVNULL, creationflags=windows_hide_flags())
                 if result.returncode != 0:
                     stderr = result.stderr.decode("utf-8", errors="ignore")[:300]
                     raise RuntimeError(f"ffmpeg conversion failed: {stderr}")
    ```

    ```diff
    @@ -1871,7 +1873,7 @@ def _generate_neutts(text: str, output_path: str, tts_config: Dict[str, Any]) ->
             ffmpeg = shutil.which("ffmpeg")
             if ffmpeg:
                 conv_cmd = [ffmpeg, "-i", wav_path, "-y", "-loglevel", "error", output_path]
    -            subprocess.run(conv_cmd, check=True, timeout=30, stdin=subprocess.DEVNULL)
    +            subprocess.run(conv_cmd, check=True, timeout=30, stdin=subprocess.DEVNULL, creationflags=windows_hide_flags())
                 os.remove(wav_path)
             else:
                 # No ffmpeg — just rename the WAV to the expected path
    ```

    ```diff
    @@ -2050,7 +2052,7 @@ def _generate_piper_tts(text: str, output_path: str, tts_config: Dict[str, Any])
             ffmpeg = shutil.which("ffmpeg")
             if ffmpeg:
                 conv_cmd = [ffmpeg, "-i", wav_path, "-y", "-loglevel", "error", output_path]
    -            subprocess.run(conv_cmd, check=True, timeout=30, stdin=subprocess.DEVNULL)
    +            subprocess.run(conv_cmd, check=True, timeout=30, stdin=subprocess.DEVNULL, creationflags=windows_hide_flags())
                 try:
                     os.remove(wav_path)
                 except OSError:
    ```

    ```diff
    @@ -2116,7 +2118,7 @@ def _generate_kittentts(text: str, output_path: str, tts_config: Dict[str, Any])
             ffmpeg = shutil.which("ffmpeg")
             if ffmpeg:
                 conv_cmd = [ffmpeg, "-i", wav_path, "-y", "-loglevel", "error", output_path]
    -            subprocess.run(conv_cmd, check=True, timeout=30, stdin=subprocess.DEVNULL)
    +            subprocess.run(conv_cmd, check=True, timeout=30, stdin=subprocess.DEVNULL, creationflags=windows_hide_flags())
                 os.remove(wav_path)
             else:
                 # No ffmpeg — rename the WAV to the expected path
    ```
- **#54236: fix(windows): hide console-window flash on backend git/gh/wmic/bash subprocess spawns**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `agent/coding_context.py`, `agent/context_references.py`, `agent/shell_hooks.py`, `agent/skill_preprocessing.py`, `gateway/status.py`, `hermes_cli/copilot_auth.py`, `hermes_cli/gateway.py`, `hermes_cli/main.py`, `tests/gateway/test_status.py`, `tests/test_windows_subprocess_no_window_flags.py`, `tui_gateway/git_probe.py`, `tui_gateway/server.py`
  - fix hunks:
  - `agent/coding_context.py`

    ```diff
    @@ -60,6 +60,8 @@
     from pathlib import Path
     from typing import Any, Optional
     
    +from hermes_cli._subprocess_compat import IS_WINDOWS, windows_hide_flags
    +
     logger = logging.getLogger("hermes.coding_context")
     
     CODING_TOOLSET = "coding"
    ```

    ```diff
    @@ -647,12 +649,14 @@ def _enabled_mcp_servers(config: Optional[dict[str, Any]]) -> list[str]:
     
     
     def _git(cwd: Path, *args: str) -> str:
    +    _popen_kwargs = {"creationflags": windows_hide_flags()} if IS_WINDOWS else {}
         try:
             out = subprocess.run(
                 ["git", "-C", str(cwd), *args],
                 capture_output=True,
                 text=True,
                 timeout=_GIT_TIMEOUT,
    +            **_popen_kwargs,
             )
         except (OSError, subprocess.SubprocessError):
             return ""
    ```
  - `agent/context_references.py`

    ```diff
    @@ -12,6 +12,7 @@
     from typing import Awaitable, Callable
     
     from agent.model_metadata import estimate_tokens_rough
    +from hermes_cli._subprocess_compat import IS_WINDOWS, windows_hide_flags
     
     _QUOTED_REFERENCE_VALUE = r'(?:`[^`\n]+`|"[^"\n]+"|\'[^\'\n]+\')'
     REFERENCE_PATTERN = re.compile(
    ```

    ```diff
    @@ -290,6 +291,7 @@ def _expand_git_reference(
         args: list[str],
         label: str,
     ) -> tuple[str | None, str | None]:
    +    _popen_kwargs = {"creationflags": windows_hide_flags()} if IS_WINDOWS else {}
         try:
             result = subprocess.run(
                 ["git", *args],
    ```

    ```diff
    @@ -298,6 +300,7 @@ def _expand_git_reference(
                 text=True,
                 timeout=30,
                 stdin=subprocess.DEVNULL,
    +            **_popen_kwargs,
             )
         except subprocess.TimeoutExpired:
             return f"{ref.raw}: git command timed out (30s)", None
    ```

    ```diff
    @@ -483,6 +486,7 @@ def _iter_visible_entries(path: Path, cwd: Path, limit: int) -> list[Path]:
     
     
     def _rg_files(path: Path, cwd: Path, limit: int) -> list[Path] | None:
    +    _popen_kwargs = {"creationflags": windows_hide_flags()} if IS_WINDOWS else {}
         try:
             result = subprocess.run(
                 ["rg", "--files", str(path.relative_to(cwd))],
    ```

    ```diff
    @@ -491,6 +495,7 @@ def _rg_files(path: Path, cwd: Path, limit: int) -> list[Path] | None:
                 text=True,
                 timeout=10,
                 stdin=subprocess.DEVNULL,
    +            **_popen_kwargs,
             )
         except (FileNotFoundError, OSError, subprocess.TimeoutExpired):
             return None
    ```
  - `agent/shell_hooks.py`

    ```diff
    @@ -122,6 +122,8 @@
     from pathlib import Path
     from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple
     
    +from hermes_cli._subprocess_compat import IS_WINDOWS, windows_hide_flags
    +
     try:
         import fcntl  # POSIX only; Windows falls back to best-effort without flock.
     except ImportError:  # pragma: no cover
    ```

    ```diff
    @@ -441,6 +443,7 @@ def _spawn(spec: ShellHookSpec, stdin_json: str) -> Dict[str, Any]:
             return result
     
         t0 = time.monotonic()
    +    _popen_kwargs = {"creationflags": windows_hide_flags()} if IS_WINDOWS else {}
         try:
             proc = subprocess.run(
                 argv,
    ```

    ```diff
    @@ -449,6 +452,7 @@ def _spawn(spec: ShellHookSpec, stdin_json: str) -> Dict[str, Any]:
                 timeout=spec.timeout,
                 text=True,
                 shell=False,
    +            **_popen_kwargs,
             )
         except subprocess.TimeoutExpired:
             result["timed_out"] = True
    ```
  - `agent/skill_preprocessing.py`

    ```diff
    @@ -5,6 +5,8 @@
     import subprocess
     from pathlib import Path
     
    +from hermes_cli._subprocess_compat import IS_WINDOWS, windows_hide_flags
    +
     logger = logging.getLogger(__name__)
     
     # Matches ${HERMES_SKILL_DIR} / ${HERMES_SESSION_ID} tokens in SKILL.md.
    ```

    ```diff
    @@ -66,6 +68,7 @@ def run_inline_shell(command: str, cwd: Path | None, timeout: int) -> str:
         Failures return a short ``[inline-shell error: ...]`` marker instead of
         raising, so one bad snippet can't wreck the whole skill message.
         """
    +    _popen_kwargs = {"creationflags": windows_hide_flags()} if IS_WINDOWS else {}
         try:
             completed = subprocess.run(
                 ["bash", "-c", command],
    ```

    ```diff
    @@ -75,6 +78,7 @@ def run_inline_shell(command: str, cwd: Path | None, timeout: int) -> str:
                 timeout=max(1, int(timeout)),
                 check=False,
                 stdin=subprocess.DEVNULL,
    +            **_popen_kwargs,
             )
         except subprocess.TimeoutExpired:
             return f"[inline-shell timeout after {timeout}s: {command}]"
    ```
  - `gateway/status.py`

    ```diff
    @@ -81,12 +81,18 @@ def terminate_pid(pid: int, *, force: bool = False) -> None:
         because os.kill(..., SIGTERM) is not equivalent to a tree-killing hard stop.
         """
         if force and _IS_WINDOWS:
    +        # CREATE_NO_WINDOW: terminate_pid runs from the windowless pythonw.exe
    +        # gateway/desktop backend, so a bare taskkill spawn would flash a
    +        # conhost window on every force-kill.
    +        from hermes_cli._subprocess_compat import windows_hide_flags
    +
             try:
                 result = subprocess.run(
                     ["taskkill", "/PID", str(pid), "/T", "/F"],
                     capture_output=True,
                     text=True,
                     timeout=10,
    +                creationflags=windows_hide_flags(),
                 )
             except FileNotFoundError:
                 os.kill(pid, signal.SIGTERM)
    ```
  - `hermes_cli/copilot_auth.py`

    ```diff
    @@ -27,6 +27,8 @@
     from pathlib import Path
     from typing import Optional
     
    +from hermes_cli._subprocess_compat import IS_WINDOWS, windows_hide_flags
    +
     logger = logging.getLogger(__name__)
     
     # OAuth device code flow constants (same client ID as opencode/Copilot CLI)
    ```

    ```diff
    @@ -130,6 +132,7 @@ def _try_gh_cli_token() -> Optional[str]:
         clean_env = {k: v for k, v in os.environ.items()
                      if k not in {"GITHUB_TOKEN", "GH_TOKEN"}}
     
    +    _popen_kwargs = {"creationflags": windows_hide_flags()} if IS_WINDOWS else {}
         for gh_path in _gh_cli_candidates():
             cmd = [gh_path, "auth", "token"]
             if hostname:
    ```

    ```diff
    @@ -141,6 +144,7 @@ def _try_gh_cli_token() -> Optional[str]:
                     text=True,
                     timeout=5,
                     env=clean_env,
    +                **_popen_kwargs,
                 )
             except (FileNotFoundError, subprocess.TimeoutExpired) as exc:
                 logger.debug("gh CLI token lookup failed (%s): %s", gh_path, exc)
    ```
  - `hermes_cli/gateway.py`

    ```diff
    @@ -384,6 +384,12 @@ def _matches_gateway_runtime(command: str) -> bool:
                 # removed as part of the WMIC deprecation — fall back to
                 # PowerShell's Get-CimInstance.  Any OSError here (FileNotFoundError
                 # on missing wmic) trips the fallback.
    +            # Hide the console window: this scan runs inside the windowless
    +            # pythonw.exe gateway/desktop backend, so a bare wmic/powershell
    +            # spawn would flash a conhost window on every watchdog probe.
    +            from hermes_cli._subprocess_compat import windows_hide_flags
    +
    +            _no_window = {"creationflags": windows_hide_flags()}
                 wmic_path = shutil.which("wmic")
                 used_fallback = False
                 result = None
    ```

    ```diff
    @@ -402,6 +408,7 @@ def _matches_gateway_runtime(command: str) -> bool:
                             encoding="utf-8",
                             errors="ignore",
                             timeout=10,
    +                        **_no_window,
                         )
                     except (OSError, subprocess.TimeoutExpired):
                         result = None
    ```

    ```diff
    @@ -427,6 +434,7 @@ def _matches_gateway_runtime(command: str) -> bool:
                             encoding="utf-8",
                             errors="ignore",
                             timeout=15,
    +                        **_no_window,
                         )
                     except (OSError, subprocess.TimeoutExpired):
                         return []
    ```
  - `hermes_cli/main.py`

    ```diff
    @@ -5825,6 +5825,11 @@ def _find_stale_dashboard_pids(
                 # here is errors="ignore": it prevents a reader-thread
                 # UnicodeDecodeError from leaving result.stdout=None and turning
                 # the later .split() into an AttributeError (#17049).
    +            # CREATE_NO_WINDOW hides the conhost flash: this scan can run from
    +            # the windowless pythonw.exe desktop/gateway backend during an
    +            # update, where a bare wmic spawn would pop a console window.
    +            from hermes_cli._subprocess_compat import windows_hide_flags
    +
                 result = subprocess.run(
                     ["wmic", "process", "get", "ProcessId,CommandLine", "/FORMAT:LIST"],
                     capture_output=True,
    ```

    ```diff
    @@ -5832,6 +5837,7 @@ def _find_stale_dashboard_pids(
                     timeout=10,
                     encoding="utf-8",
                     errors="ignore",
    +                creationflags=windows_hide_flags(),
                 )
                 if result.returncode != 0 or result.stdout is None:
                     return []
    ```
  - `tests/gateway/test_status.py`

    ```diff
    @@ -621,16 +621,22 @@ def test_force_uses_taskkill_on_windows(self, monkeypatch):
             calls = []
             monkeypatch.setattr(status, "_IS_WINDOWS", True)
     
    -        def fake_run(cmd, capture_output=False, text=False, timeout=None):
    -            calls.append((cmd, capture_output, text, timeout))
    +        def fake_run(cmd, capture_output=False, text=False, timeout=None, creationflags=0):
    +            calls.append((cmd, capture_output, text, timeout, creationflags))
                 return SimpleNamespace(returncode=0, stdout="", stderr="")
     
             monkeypatch.setattr(status.subprocess, "run", fake_run)
     
             status.terminate_pid(123, force=True)
     
    +        # taskkill is spawned with the no-window flag so the windowless
    +        # pythonw.exe backend doesn't flash a conhost window on force-kill.
    +        # windows_hide_flags() is 0 on the POSIX test host (a valid no-op
    +        # creationflags value); on real Windows it is CREATE_NO_WINDOW.
    +        from hermes_cli._subprocess_compat import windows_hide_flags
    +
             assert calls == [
    -            (["taskkill", "/PID", "123", "/T", "/F"], True, True, 10)
    +            (["taskkill", "/PID", "123", "/T", "/F"], True, True, 10, windows_hide_flags())
             ]
     
         def test_force_falls_back_to_sigterm_when_taskkill_missing(self, monkeypatch):
    ```
  - `tests/test_windows_subprocess_no_window_flags.py`

    ```diff
    @@ -0,0 +1,263 @@
    +from __future__ import annotations
    +
    +import subprocess
    +from pathlib import Path
    +from types import SimpleNamespace
    +
    +
    +_CREATE_NO_WINDOW = 0x08000000
    +
    +
    +class _Completed:
    +    def __init__(self, stdout: str | bytes = "ok\n", returncode: int = 0):
    +        self.stdout = stdout
    +        self.stderr = ""
    +        self.returncode = returncode
    +
    +
    +def test_tui_gateway_git_probe_hides_git_windows(monkeypatch):
    +    from tui_gateway import git_probe
    +
    +    captured = []
    +
    +    def fake_run(cmd, **kwargs):
    +        captured.append((cmd, kwargs))
    +        return _Completed(stdout="main\n")
    +
    +    monkeypatch.setattr(git_probe, "IS_WINDOWS", True)
    +    monkeypatch.setattr(git_probe, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
    +    monkeypatch.setattr(git_probe.subprocess, "run", fake_run)
    +
    +    assert git_probe.run_git("C:/repo", "branch", "--show-current") == "main"
    +
    +    assert captured == [
    +        (
    +            ["git", "-C", "C:/repo", "branch", "--show-current"],
    +            {
    +                "capture_output": True,
    +                "text": True,
    +                "encoding": "utf-8",
    +                "errors": "replace",
    +                "timeout": git_probe._GIT_TIMEOUT,
    +                "check": False,
    +                "stdin": subprocess.DEVNULL,
    +                "creationflags": _CREATE_NO_WINDOW,
    +            },
    +        )
    +    ]
    +
    +
    +def test_tui_gateway_fuzzy_file_listing_hides_git_windows(monkeypatch):
    +    from hermes_cli import _subprocess_compat
    +    from tui_gateway import server
    +
    +    captured = []
    +
    +    def fake_run(cmd, **kwargs):
    +        captured.append((cmd, kwargs))
    +        if cmd[-1] == "--show-toplevel":
    +            return _Completed(stdout=b"C:/repo\n")
    +        return _Completed(stdout=b"src/main.py\0README.md\0")
    +
    +    monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", True)
    +    monkeypatch.setattr(_subprocess_compat, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
    +    monkeypatch.setattr(server.subprocess, "run", fake_run)
    +    server._fuzzy_cache.clear()
    +
    +    assert server._list_repo_files("C:/repo") == ["src/main.py", "README.md"]
    +
    +    assert [kwargs["creationflags"] for _, kwargs in captured] == [
    +        _CREATE_NO_WINDOW,
    +        _CREATE_NO_WINDOW,
    +    ]
    +
    +
    +def test_coding_context_git_hides_git_windows(monkeypatch):
    +    from agent import coding_context
    +
    +    captured = []
    +
    +    def fake_run(cmd, **kwargs):
    +        captured.append((cmd, kwargs))
    +        return _Completed(stdout="clean\n")
    +
    +    monkeypatch.setattr(coding_context, "IS_WINDOWS", True)
    +    monkeypatch.setattr(coding_context, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
    +    monkeypatch.setattr(coding_context.subprocess, "run", fake_run)
    +
    +    assert coding_context._git(Path("C:/repo"), "status", "--short") == "clean"
    +    assert captured[0][1]["creationflags"] == _CREATE_NO_WINDOW
    +
    +
    +def test_context_reference_git_and_rg_hide_windows(monkeypatch):
    +    from agent import context_references
    +
    +    captured = []
    +
    +    def fake_run(cmd, **kwargs):
    +        captured.append((cmd, kwargs))
    +        if cmd[0] == "rg":
    +            return _Completed(stdout="src/main.py\n")
    +        return _Completed(stdout="diff --git a/src/main.py b/src/main.py\n")
    +
    +    monkeypatch.setattr(context_references, "IS_WINDOWS", True)
    +    monkeypatch.setattr(context_references, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
    +    monkeypatch.setattr(context_references.subprocess, "run", fake_run)
    +
    +    ref = context_references.ContextReference(
    +        raw="@diff",
    +        kind="diff",
    +        target="",
    +        start=0,
    +        end=5,
    +    )
    +    warning, block = context_references._expand_git_reference(
    +        ref,
    +        Path("C:/repo"),
    +        ["diff"],
    +        "git diff",
    +    )
    +    assert warning is None
    +    assert block is not None
    +    assert "git diff" in block
    +    assert context_references._rg_files(Path("C:/repo/src"), Path("C:/repo"), 10) == [
    +        Path("src/main.py")
    +    ]
    +
    +    assert [kwargs["creationflags"] for _, kwargs in captured] == [
    +        _CREATE_NO_WINDOW,
    +        _CREATE_NO_WINDOW,
    +    ]
    +
    +
    +def test_copilot_gh_cli_probe_hides_gh_windows(monkeypatch):
    +    from hermes_cli import copilot_auth
    +
    +    captured = []
    +
    +    def fake_run(cmd, **kwargs):
    +        captured.append((cmd, kwargs))
    +        return _Completed(stdout="gho_from_cli\n")
    +
    +    monkeypatch.setattr(copilot_auth, "IS_WINDOWS", True)
    +    monkeypatch.setattr(copilot_auth, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
    +    monkeypatch.setattr(copilot_auth, "_gh_cli_candidates", lambda: ["gh"])
    +    monkeypatch.setattr(copilot_auth.subprocess, "run", fake_run)
    +
    +    assert copilot_auth._try_gh_cli_token() == "gho_from_cli"
    +    assert captured[0][0] == ["gh", "auth", "token"]
    +    assert captured[0][1]["creationflags"] == _CREATE_NO_WINDOW
    +
    +
    +def test_gateway_pid_scan_hides_wmic_and_powershell_windows(monkeypatch):
    +    from hermes_cli import gateway
    +    from hermes_cli import _subprocess_compat
    +
    +    captured = []
    +
    +    def fake_run(cmd, **kwargs):
    +        captured.append((cmd, kwargs))
    +        if cmd[0] == "wmic":
    +            return _Completed(stdout="", returncode=1)
    +        return _Completed(stdout="CommandLine=hermes gateway\nProcessId=123\n")
    +
    +    monkeypatch.setattr(gateway, "is_windows", lambda: True)
    +    monkeypatch.setattr(gateway.shutil, "which", lambda name: name)
    +    monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", True)
    +    monkeypatch.setattr(_subprocess_compat, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
    +    monkeypatch.setattr(gateway.subprocess, "run", fake_run)
    +
    +    assert gateway._scan_gateway_pids(set()) == [123]
    +    assert [kwargs["creationflags"] for _, kwargs in captured] == [
    +        _CREATE_NO_WINDOW,
    +        _CREATE_NO_WINDOW,
    +    ]
    +
    +
    +def test_stale_dashboard_windows_scan_hides_wmic(monkeypatch):
    +    from hermes_cli import main
    +    from hermes_cli import _subprocess_compat
    +
    +    captured = []
    +
    +    def fake_run(cmd, **kwargs):
    +        captured.append((cmd, kwargs))
    +        return _Completed(stdout="CommandLine=hermes dashboard\nProcessId=123\n")
    +
    +    monkeypatch.setattr(main.sys, "platform", "win32")
    +    monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", True)
    +    monkeypatch.setattr(_subprocess_compat, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
    +    monkeypatch.setattr(main.subprocess, "run", fake_run)
    +
    +    assert main._find_stale_dashboard_pids() == [123]
    +    assert captured[0][1]["creationflags"] == _CREATE_NO_WINDOW
    +
    +
    +def test_gateway_force_kill_hides_taskkill_window(monkeypatch):
    +    from gateway import status
    +    from hermes_cli import _subprocess_compat
    +
    +    captured = []
    +
    +    def fake_run(cmd, **kwargs):
    +        captured.append((cmd, kwargs))
    +        return _Completed(stdout="")
    +
    +    monkeypatch.setattr(status, "_IS_WINDOWS", True)
    +    monkeypatch.setattr(_subprocess_compat, "IS_WINDOWS", True)
    +    monkeypatch.setattr(_subprocess_compat, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
    +    monkeypatch.setattr(status.subprocess, "run", fake_run)
    +
    +    status.terminate_pid(123, force=True)
    +
    +    assert captured == [
    +        (
    +            ["taskkill", "/PID", "123", "/T", "/F"],
    +            {
    +                "capture_output": True,
    +                "text": True,
    +                "timeout": 10,
    +                "creationflags": _CREATE_NO_WINDOW,
    +            },
    +        )
    +    ]
    +
    +
    +def test_shell_hooks_hide_hook_command_windows(monkeypatch):
    +    from agent import shell_hooks
    +
    +    captured = []
    +
    +    def fake_run(cmd, **kwargs):
    +        captured.append((cmd, kwargs))
    +        return SimpleNamespace(returncode=0, stdout="{}", stderr="")
    +
    +    monkeypatch.setattr(shell_hooks, "IS_WINDOWS", True)
    +    monkeypatch.setattr(shell_hooks, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
    +    monkeypatch.setattr(shell_hooks.subprocess, "run", fake_run)
    +
    +    result = shell_hooks._spawn(
    +        shell_hooks.ShellHookSpec(event="post_tool_call", command="hook-bin --flag"),
    +        "{}",
    +    )
    +
    +    assert result["returncode"] == 0
    +    assert captured[0][1]["creationflags"] == _CREATE_NO_WINDOW
    +
    +
    +def test_inline_skill_shell_hides_bash_window(monkeypatch):
    +    from agent import skill_preprocessing
    +
    +    captured = []
    +
    +    def fake_run(cmd, **kwargs):
    +        captured.append((cmd, kwargs))
    +        return SimpleNamespace(returncode=0, stdout="ok\n", stderr="")
    +
    +    monkeypatch.setattr(skill_preprocessing, "IS_WINDOWS", True)
    +    monkeypatch.setattr(skill_preprocessing, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
    +    monkeypatch.setattr(skill_preprocessing.subprocess, "run", fake_run)
    +
    +    assert skill_preprocessing.run_inline_shell("echo ok", cwd=None, timeout=5) == "ok"
    +    assert captured[0][0] == ["bash", "-c", "echo ok"]
    +    assert captured[0][1]["creationflags"] == _CREATE_NO_WINDOW
    ```
  - `tui_gateway/git_probe.py`

    ```diff
    @@ -31,6 +31,8 @@
     from collections.abc import Iterable
     from concurrent.futures import ThreadPoolExecutor
     
    +from hermes_cli._subprocess_compat import IS_WINDOWS, windows_hide_flags
    +
     _GIT_TIMEOUT = 1.5
     _WARM_WORKERS = 8
     
    ```

    ```diff
    @@ -45,14 +47,18 @@ def run_git(cwd: str, *args: str) -> str:
         """``git -C <cwd> <args>`` → stripped stdout, or ``""`` on any failure."""
         if not cwd:
             return ""
    +    _popen_kwargs = {"creationflags": windows_hide_flags()} if IS_WINDOWS else {}
         try:
             result = subprocess.run(
                 ["git", "-C", cwd, *args],
                 capture_output=True,
                 text=True,
    +            encoding="utf-8",
    +            errors="replace",
                 timeout=_GIT_TIMEOUT,
                 check=False,
                 stdin=subprocess.DEVNULL,
    +            **_popen_kwargs,
             )
             return result.stdout.strip() if result.returncode == 0 else ""
         except Exception:
    ```
  - `tui_gateway/server.py`

    ```diff
    @@ -11668,6 +11668,9 @@ def _list_repo_files(root: str) -> list[str]:
                 return cached[1]
     
         files: list[str] = []
    +    from hermes_cli._subprocess_compat import windows_hide_flags
    +
    +    _creationflags = windows_hide_flags()
         try:
             top_result = subprocess.run(
                 ["git", "-C", root, "rev-parse", "--show-toplevel"],
    ```

    ```diff
    @@ -11675,6 +11678,7 @@ def _list_repo_files(root: str) -> list[str]:
                 timeout=2.0,
                 check=False,
                 stdin=subprocess.DEVNULL,
    +            creationflags=_creationflags,
             )
             if top_result.returncode == 0:
                 top = top_result.stdout.decode("utf-8", "replace").strip()
    ```

    ```diff
    @@ -11693,6 +11697,7 @@ def _list_repo_files(root: str) -> list[str]:
                     timeout=2.0,
                     check=False,
                     stdin=subprocess.DEVNULL,
    +                creationflags=_creationflags,
                 )
                 if list_result.returncode == 0:
                     for p in list_result.stdout.decode("utf-8", "replace").split("\0"):
    ```
- **#54253: fix(win32): add CREATE_NO_WINDOW to all background subprocess calls**. closed without merge · fix tier `suggested`
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `cb982ad99`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#54417: fix(windows): cover remaining console-flash spawn legs**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `gateway/run.py`, `gateway/status.py`, `hermes_cli/gateway.py`, `hermes_constants.py`, `tests/gateway/test_restart_drain.py`, `tests/gateway/test_status.py`, `tests/test_hermes_constants.py`, `tests/test_windows_subprocess_no_window_flags.py`, `tests/tools/test_browser_chromium_check.py`, `tests/tools/test_browser_homebrew_paths.py`, `tools/browser_tool.py`, `tui_gateway/server.py`
  - fix hunks:
  - `gateway/run.py`

    ```diff
    @@ -5501,6 +5501,19 @@ def _alive(p):
                 # run as a self-restart loop guard and the gateway stays stopped.
                 watcher_env.pop("_HERMES_GATEWAY", None)
                 project_root = Path(__file__).resolve().parent.parent
    +            watcher_python = sys.executable
    +            try:
    +                # Prefer a real GUI-subsystem interpreter for the watcher
    +                # itself.  With uv venvs, ``python.exe`` can re-exec the base
    +                # console interpreter and flash even when the Popen carries
    +                # CREATE_NO_WINDOW; pythonw.exe avoids console allocation.
    +                from hermes_cli.gateway_windows import _resolve_detached_python
    +
    +                watcher_python, _watcher_venv_dir, _watcher_site_packages = (
    +                    _resolve_detached_python(sys.executable)
    +                )
    +            except Exception:
    +                watcher_python = sys.executable
                 venv_dir = Path(watcher_env.get("VIRTUAL_ENV") or project_root / "venv")
                 site_packages = venv_dir / "Lib" / "site-packages"
                 if site_packages.exists():
    ```

    ```diff
    @@ -5510,7 +5523,7 @@ def _alive(p):
                         pythonpath.append(watcher_env["PYTHONPATH"])
                     watcher_env["PYTHONPATH"] = os.pathsep.join(dict.fromkeys(pythonpath))
                 subprocess.Popen(
    -                [sys.executable, "-c", watcher, str(current_pid), str(restart_after_s), *cmd_argv],
    +                [watcher_python, "-c", watcher, str(current_pid), str(restart_after_s), *cmd_argv],
                     stdout=subprocess.DEVNULL,
                     stderr=subprocess.DEVNULL,
                     env=watcher_env,
    ```
  - `gateway/status.py`

    ```diff
    @@ -171,17 +171,18 @@ def _read_process_cmdline(pid: int) -> Optional[str]:
             if raw:
                 return raw.replace(b"\x00", b" ").decode("utf-8", errors="ignore").strip()
     
    -    try:
    -        result = subprocess.run(
    -            ["ps", "-p", str(pid), "-o", "command="],
    -            capture_output=True,
    -            text=True,
    -            timeout=5,
    -        )
    -        if result.returncode == 0 and result.stdout.strip():
    -            return result.stdout.strip()
    -    except (OSError, subprocess.TimeoutExpired):
    -        pass
    +    if not _IS_WINDOWS:
    +        try:
    +            result = subprocess.run(
    +                ["ps", "-p", str(pid), "-o", "command="],
    +                capture_output=True,
    +                text=True,
    +                timeout=5,
    +            )
    +            if result.returncode == 0 and result.stdout.strip():
    +                return result.stdout.strip()
    +        except (OSError, subprocess.TimeoutExpired):
    +            pass
     
         # Windows fallback: psutil (already used by _pid_exists)
         try:
    ```
  - `hermes_cli/gateway.py`

    ```diff
    @@ -180,7 +180,11 @@ def _get_parent_pid(pid: int) -> int | None:
             pass
         except Exception:
             return None
    -    # Fallback: shell out to ps (POSIX only — bare ``ps`` doesn't exist on Windows).
    +    # Fallback: shell out to ps (POSIX only).  Git Bash installs ``ps.exe`` on
    +    # Windows; running it from the windowless desktop/gateway backend flashes a
    +    # console, and psutil above is the authoritative Windows path anyway.
    +    if is_windows():
    +        return None
         if not shutil.which("ps"):
             return None
         try:
    ```
  - `hermes_constants.py`

    ```diff
    @@ -317,11 +317,14 @@ def node_tool_runnable(path: str | None) -> bool:
         import subprocess
     
         try:
    +        from hermes_cli._subprocess_compat import windows_hide_flags
    +
             result = subprocess.run(
                 [path, "--version"],
                 capture_output=True,
                 timeout=10,
                 env=with_hermes_node_path(),
    +            creationflags=windows_hide_flags(),
             )
         except (OSError, subprocess.TimeoutExpired, ValueError):
             return False
    ```

    ```diff
    @@ -562,11 +565,14 @@ def agent_browser_runnable(path: str | None) -> bool:
         import subprocess
     
         try:
    +        from hermes_cli._subprocess_compat import windows_hide_flags
    +
             result = subprocess.run(
                 [path, "--version"],
                 capture_output=True,
                 timeout=10,
                 env=with_hermes_node_path(),
    +            creationflags=windows_hide_flags(),
             )
         except (OSError, subprocess.TimeoutExpired, ValueError):
             return False
    ```
  - `tests/gateway/test_restart_drain.py`

    ```diff
    @@ -353,6 +353,49 @@ def fake_popen(cmd, **kwargs):
         assert kwargs["stderr"] is subprocess.DEVNULL
     
     
    [redacted:email]
    +async def test_windows_detached_restart_uses_pythonw_for_watcher(monkeypatch, tmp_path):
    +    runner, _adapter = make_restart_runner()
    +    popen_calls = []
    +    venv_dir = tmp_path / "venv"
    +    site_packages = venv_dir / "Lib" / "site-packages"
    +    site_packages.mkdir(parents=True)
    +
    +    monkeypatch.setattr(gateway_run.sys, "platform", "win32")
    +    monkeypatch.setattr(gateway_run.sys, "executable", r"C:\venv\Scripts\python.exe")
    +    monkeypatch.setattr(gateway_run, "_resolve_hermes_bin", lambda: ["hermes"])
    +    monkeypatch.setattr(gateway_run.os, "getpid", lambda: 321)
    +    monkeypatch.setenv("VIRTUAL_ENV", str(venv_dir))
    +
    +    import hermes_cli._subprocess_compat as subprocess_compat
    +    import hermes_cli.gateway_windows as gateway_windows
    +
    +    monkeypatch.setattr(
    +        gateway_windows,
    +        "_resolve_detached_python",
    +        lambda _python: (r"C:\Python311\pythonw.exe", venv_dir, [str(site_packages)]),
    +    )
    +    monkeypatch.setattr(
    +        subprocess_compat,
    +        "windows_detach_popen_kwargs",
    +        lambda: {"creationflags": 0x08000008},
    +    )
    +
    +    def fake_popen(cmd, **kwargs):
    +        popen_calls.append((cmd, kwargs))
    +        return MagicMock()
    +
    +    monkeypatch.setattr(subprocess, "Popen", fake_popen)
    +
    +    await runner._launch_detached_restart_command()
    +
    +    assert len(popen_calls) == 1
    +    cmd, kwargs = popen_calls[0]
    +    assert cmd[0] == r"C:\Python311\pythonw.exe"
    +    assert cmd[-3:] == ["hermes", "gateway", "restart"]
    +    assert kwargs["creationflags"] == 0x08000008
    +
    +
     # ── Shutdown notification tests ──────────────────────────────────────
     
     
    ```
  - `tests/gateway/test_status.py`

    ```diff
    @@ -2,6 +2,7 @@
     
     import json
     import os
    +import sys
     from pathlib import Path
     from types import SimpleNamespace
     
    ```

    ```diff
    @@ -1351,6 +1352,7 @@ class TestReadProcessCmdlinePsFallback:
     
         def test_ps_fallback_when_proc_unavailable(self, monkeypatch):
             monkeypatch.setattr(status.Path, "read_bytes", lambda self: (_ for _ in ()).throw(FileNotFoundError))
    +        monkeypatch.setattr(status, "_IS_WINDOWS", False)
             monkeypatch.setattr(
                 status.subprocess, "run",
                 lambda args, **kwargs: SimpleNamespace(returncode=0, stdout="/usr/libexec/bluetoothuserd\n"),
    ```

    ```diff
    @@ -1360,6 +1362,7 @@ def test_ps_fallback_when_proc_unavailable(self, monkeypatch):
     
         def test_ps_fallback_returns_none_on_failure(self, monkeypatch):
             monkeypatch.setattr(status.Path, "read_bytes", lambda self: (_ for _ in ()).throw(FileNotFoundError))
    +        monkeypatch.setattr(status, "_IS_WINDOWS", False)
             monkeypatch.setattr(
                 status.subprocess, "run",
                 lambda args, **kwargs: SimpleNamespace(returncode=1, stdout=""),
    ```

    ```diff
    @@ -1381,6 +1384,7 @@ def fake_read_bytes(self):
     
         def test_ps_fallback_used_when_proc_returns_empty(self, monkeypatch):
             monkeypatch.setattr(status.Path, "read_bytes", lambda self: b"")
    +        monkeypatch.setattr(status, "_IS_WINDOWS", False)
             monkeypatch.setattr(
                 status.subprocess, "run",
                 lambda args, **kwargs: SimpleNamespace(returncode=0, stdout="python hermes_cli/main.py gateway run\n"),
    ```

    ```diff
    @@ -1388,6 +1392,34 @@ def test_ps_fallback_used_when_proc_returns_empty(self, monkeypatch):
             result = status._read_process_cmdline(12345)
             assert "hermes_cli/main.py" in result
     
    +    def test_windows_skips_ps_fallback_and_uses_psutil(self, monkeypatch):
    +        monkeypatch.setattr(status.Path, "read_bytes", lambda self: (_ for _ in ()).throw(FileNotFoundError))
    +        monkeypatch.setattr(status, "_IS_WINDOWS", True)
    +        ps_calls = []
    +        monkeypatch.setattr(
    +            status.subprocess,
    +            "run",
    +            lambda args, **kwargs: ps_calls.append((args, kwargs)) or SimpleNamespace(returncode=0, stdout="ps should not run\n"),
    +        )
    +
    +        class _Proc:
    +            def __init__(self, pid):
    +                self.pid = pid
    +
    +            def cmdline(self):
    +                return ["pythonw.exe", "-m", "hermes_cli.main", "gateway", "run"]
    +
    +        monkeypatch.setitem(
    +            sys.modules,
    +            "psutil",
    +            SimpleNamespace(Process=_Proc),
    +        )
    +
    +        result = status._read_process_cmdline(12345)
    +
    +        assert result == "pythonw.exe -m hermes_cli.main gateway run"
    +        assert ps_calls == []
    +
     
     class TestCorruptStatusFiles:
         """A status / pid file holding non-UTF-8 (binary) bytes must read as
    ```
  - `tests/test_hermes_constants.py`

    ```diff
    @@ -2,6 +2,7 @@
     
     import os
     from pathlib import Path
    +from types import SimpleNamespace
     
     import pytest
     
    ```

    ```diff
    @@ -611,6 +612,43 @@ def test_npx_fallback_form_accepted(self):
             assert agent_browser_runnable("npx agent-browser") is True
             assert agent_browser_runnable("/usr/local/bin/npx agent-browser") is True
     
    +    def test_version_probe_uses_windows_hide_flags(self, tmp_path, monkeypatch):
    +        good = self._stub(tmp_path, "agent-browser", "#!/bin/sh\necho hi\n")
    +        captured = []
    +
    +        def fake_run(cmd, **kwargs):
    +            captured.append((cmd, kwargs))
    +            return SimpleNamespace(returncode=0)
    +
    +        import hermes_cli._subprocess_compat as subprocess_compat
    +        import subprocess as subprocess_mod
    +
    +        monkeypatch.setattr(subprocess_compat, "windows_hide_flags", lambda: 0x08000000)
    +        monkeypatch.setattr(subprocess_mod, "run", fake_run)
    +
    +        assert agent_browser_runnable(str(good)) is True
    +        assert captured[0][0] == [str(good), "--version"]
    +        assert captured[0][1]["creationflags"] == 0x08000000
    +
    +
    +    def test_node_tool_probe_uses_windows_hide_flags(self, tmp_path, monkeypatch):
    +        good = self._stub(tmp_path, "node", "#!/bin/sh\necho v22\n")
    +        captured = []
    +
    +        def fake_run(cmd, **kwargs):
    +            captured.append((cmd, kwargs))
    +            return SimpleNamespace(returncode=0)
    +
    +        import hermes_cli._subprocess_compat as subprocess_compat
    +        import subprocess as subprocess_mod
    +
    +        monkeypatch.setattr(subprocess_compat, "windows_hide_flags", lambda: 0x08000000)
    +        monkeypatch.setattr(subprocess_mod, "run", fake_run)
    +
    +        assert node_tool_runnable(str(good)) is True
    +        assert captured[0][0] == [str(good), "--version"]
    +        assert captured[0][1]["creationflags"] == 0x08000000
    +
     
     class TestGetHermesDir:
         """Tests for ``get_hermes_dir(new_subpath, old_name)``.
    ```
  - `tests/test_windows_subprocess_no_window_flags.py`

    ```diff
    @@ -299,3 +299,29 @@ def fake_run(cmd, **kwargs):
     
         assert captured[0][0][0] == "ffmpeg"
         assert captured[0][1]["creationflags"] == _CREATE_NO_WINDOW
    +
    +def test_tui_slash_worker_hides_python_window(monkeypatch):
    +    from tui_gateway import server
    +
    +    captured = []
    +
    +    class _Proc:
    +        stdin = SimpleNamespace()
    +        stdout = []
    +        stderr = []
    +
    +    def fake_popen(cmd, **kwargs):
    +        captured.append((cmd, kwargs))
    +        return _Proc()
    +
    +    monkeypatch.setattr(server.subprocess, "Popen", fake_popen)
    +    monkeypatch.setattr(server.threading, "Thread", lambda *a, **k: SimpleNamespace(start=lambda: None))
    +
    +    import hermes_cli._subprocess_compat as subprocess_compat
    +
    +    monkeypatch.setattr(subprocess_compat, "windows_hide_flags", lambda: _CREATE_NO_WINDOW)
    +
    +    server._SlashWorker("session-key", "model-x")
    +
    +    assert captured[0][0][:3] == [server.sys.executable, "-m", "tui_gateway.slash_worker"]
    +    assert captured[0][1]["creationflags"] == _CREATE_NO_WINDOW
    ```
  - `tests/tools/test_browser_chromium_check.py`

    ```diff
    @@ -76,7 +76,7 @@ class TestCheckBrowserRequirementsChromium:
     
         def test_local_mode_with_chromium_returns_true(self, monkeypatch, tmp_path):
             monkeypatch.setattr(bt, "_is_camofox_mode", lambda: False)
    -        monkeypatch.setattr(bt, "_find_agent_browser", lambda: "/usr/local/bin/agent-browser")
    +        monkeypatch.setattr(bt, "_find_agent_browser", lambda **_kw: "/usr/local/bin/agent-browser")
             monkeypatch.setattr(bt, "_requires_real_termux_browser_install", lambda _: False)
             monkeypatch.setattr(bt, "_get_cloud_provider", lambda: None)
             monkeypatch.setenv("PLAYWRIGHT_BROWSERS_PATH", str(tmp_path))
    ```

    ```diff
    @@ -93,7 +93,7 @@ def provider_name(self):
                     return "browserbase"
     
             monkeypatch.setattr(bt, "_is_camofox_mode", lambda: False)
    -        monkeypatch.setattr(bt, "_find_agent_browser", lambda: "/usr/local/bin/agent-browser")
    +        monkeypatch.setattr(bt, "_find_agent_browser", lambda **_kw: "/usr/local/bin/agent-browser")
             monkeypatch.setattr(bt, "_requires_real_termux_browser_install", lambda _: False)
             monkeypatch.setattr(bt, "_get_cloud_provider", lambda: FakeProvider())
             # Point chromium search at an empty dir — should not matter for cloud.
    ```

    ```diff
    @@ -102,6 +102,23 @@ def provider_name(self):
     
             assert bt.check_browser_requirements() is True
     
    +    def test_startup_check_uses_lightweight_agent_browser_lookup(self, monkeypatch, tmp_path):
    +        seen = []
    +
    +        def fake_find_agent_browser(**kwargs):
    +            seen.append(kwargs)
    +            return "/usr/local/bin/agent-browser"
    +
    +        monkeypatch.setattr(bt, "_is_camofox_mode", lambda: False)
    +        monkeypatch.setattr(bt, "_find_agent_browser", fake_find_agent_browser)
    +        monkeypatch.setattr(bt, "_requires_real_termux_browser_install", lambda _: False)
    +        monkeypatch.setattr(bt, "_get_cloud_provider", lambda: None)
    +        monkeypatch.setenv("PLAYWRIGHT_BROWSERS_PATH", str(tmp_path))
    +        (tmp_path / "chromium-1208").mkdir()
    +
    +        assert bt.check_browser_requirements() is True
    +        assert seen == [{"validate": False}]
    +
         def test_camofox_mode_does_not_require_chromium(self, monkeypatch, tmp_path):
             monkeypatch.setattr(bt, "_is_camofox_mode", lambda: True)
             # Even with no chromium on disk, camofox drives its own backend.
    ```
  - `tests/tools/test_browser_homebrew_paths.py`

    ```diff
    @@ -222,7 +222,7 @@ def test_termux_requires_real_agent_browser_install_not_npx_fallback(self, monke
             monkeypatch.setenv("PREFIX", "/data/data/com.termux/files/usr")
             monkeypatch.setattr("tools.browser_tool._is_camofox_mode", lambda: False)
             monkeypatch.setattr("tools.browser_tool._get_cloud_provider", lambda: None)
    -        monkeypatch.setattr("tools.browser_tool._find_agent_browser", lambda: "npx agent-browser")
    +        monkeypatch.setattr("tools.browser_tool._find_agent_browser", lambda **_kw: "npx agent-browser")
     
             assert check_browser_requirements() is False
     
    ```

    ```diff
    @@ -231,7 +231,7 @@ class TestRunBrowserCommandTermuxFallback:
         def test_termux_local_mode_rejects_bare_npx_fallback(self, monkeypatch):
             monkeypatch.setenv("TERMUX_VERSION", "0.118.3")
             monkeypatch.setenv("PREFIX", "/data/data/com.termux/files/usr")
    -        monkeypatch.setattr("tools.browser_tool._find_agent_browser", lambda: "npx agent-browser")
    +        monkeypatch.setattr("tools.browser_tool._find_agent_browser", lambda **_kw: "npx agent-browser")
             monkeypatch.setattr("tools.browser_tool._get_cloud_provider", lambda: None)
     
             result = _run_browser_command("task-1", "navigate", ["https://example.com"])
    ```
  - `tools/browser_tool.py`

    ```diff
    @@ -2002,7 +2002,15 @@ def _get_session_info(task_id: Optional[str] = None) -> Dict[str, str]:
     
     
     
    -def _find_agent_browser() -> str:
    +def _agent_browser_candidate_present(path: str | None) -> bool:
    +    if not path:
    +        return False
    +    if " " in path and path.split()[0].endswith("npx"):
    +        return True
    +    return os.path.exists(path) and (os.name == "nt" or os.access(path, os.X_OK))
    +
    +
    +def _find_agent_browser(*, validate: bool = True) -> str:
         """
         Find the agent-browser CLI executable.
     
    ```

    ```diff
    @@ -2041,7 +2049,11 @@ def _find_agent_browser() -> str:
     
         # Check if it's in PATH (global install)
         which_result = shutil.which("agent-browser")
    -    if which_result and agent_browser_runnable(which_result):
    +    if which_result and (
    +        agent_browser_runnable(which_result) if validate else _agent_browser_candidate_present(which_result)
    +    ):
    +        if not validate:
    +            return which_result
             _cached_agent_browser = which_result
             _agent_browser_resolved = True
             return which_result
    ```

    ```diff
    @@ -2051,7 +2063,11 @@ def _find_agent_browser() -> str:
         extended_path = _merge_browser_path("")
         if extended_path:
             which_result = shutil.which("agent-browser", path=extended_path)
    -        if which_result and agent_browser_runnable(which_result):
    +        if which_result and (
    +            agent_browser_runnable(which_result) if validate else _agent_browser_candidate_present(which_result)
    +        ):
    +            if not validate:
    +                return which_result
                 _cached_agent_browser = which_result
                 _agent_browser_resolved = True
                 return which_result
    ```

    ```diff
    @@ -2068,7 +2084,11 @@ def _find_agent_browser() -> str:
         local_bin_dir = repo_root / "node_modules" / ".bin"
         if local_bin_dir.is_dir():
             local_which = shutil.which("agent-browser", path=str(local_bin_dir))
    -        if local_which and agent_browser_runnable(local_which):
    +        if local_which and (
    +            agent_browser_runnable(local_which) if validate else _agent_browser_candidate_present(local_which)
    +        ):
    +            if not validate:
    +                return local_which
                 _cached_agent_browser = local_which
                 _agent_browser_resolved = True
                 return _cached_agent_browser
    ```

    ```diff
    @@ -2078,10 +2098,15 @@ def _find_agent_browser() -> str:
         if not npx_path and extended_path:
             npx_path = shutil.which("npx", path=extended_path)
         if npx_path:
    +        if not validate:
    +            return "npx agent-browser"
             _cached_agent_browser = "npx agent-browser"
             _agent_browser_resolved = True
             return _cached_agent_browser
     
    +    if not validate:
    +        raise FileNotFoundError("agent-browser CLI not found")
    +
         # Nothing found — try lazy installation before giving up.
         try:
             from hermes_cli.dep_ensure import ensure_dependency
    ```

    ```diff
    @@ -4206,8 +4231,12 @@ def check_browser_requirements() -> bool:
             return True
     
         # The agent-browser CLI is required for local launch and cloud-provider flows.
    +    # Tool-schema assembly runs during Desktop startup; do not execute
    +    # ``agent-browser --version`` here, because Windows .cmd shims route through
    +    # cmd.exe and can flash a console before the user invokes any browser tool.
    +    # Actual browser execution paths still validate the candidate before use.
         try:
    -        browser_cmd = _find_agent_browser()
    +        browser_cmd = _find_agent_browser(validate=False)
         except FileNotFoundError:
             return False
     
    ```
  - `tui_gateway/server.py`

    ```diff
    @@ -278,6 +278,8 @@ def __init__(self, session_key: str, model: str):
                 argv += ["--model", model]
     
             self._closed = False
    +        from hermes_cli._subprocess_compat import windows_hide_flags
    +
             self.proc = subprocess.Popen(
                 argv,
                 stdin=subprocess.PIPE,
    ```

    ```diff
    @@ -289,6 +291,7 @@ def __init__(self, session_key: str, model: str):
                 # slash_worker runs the Hermes agent → needs provider credentials.
                 # Tier-1 secrets (gateway/GitHub/infra) are still stripped (#29157).
                 env=hermes_subprocess_env(inherit_credentials=[redacted:secret-assignment]
    +            creationflags=windows_hide_flags(),
             )
             threading.Thread(target=self._drain_stdout, daemon=True).start()
             threading.Thread(target=self._drain_stderr, daemon=True).start()
    ```
- **#64081: fix: add CREATE_NO_WINDOW to env_probe and lazy_deps subprocess calls on Windows**. closed without merge · fix tier `suggested`
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `aa2ae36c3fcb6ea9af76c603d262389d832a24dd`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#49931: fix(terminal): bypass Git Bash wrapper to prevent console flashes on Windows**. closed without merge
  - maintainer direction: category `silent_close`; implication `active`
- **#53119: fix: add CREATE_NO_WINDOW to Windows subprocess Popen calls to prevent cmd.exe flashing**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#54220`, `#54236`, `#53892`, `#54417`
- **#53123: fix(windows): suppress console-window flashes from gh/git probes in desktop gateway**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `#53844`, `#53791`, `#53810`, `#53829`
- **#53291: fix: suppress console window flashes on Windows via global subprocess patch (#53282)**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#54220`, `#54236`, `#53892`, `#54417`
- **#53344: fix(desktop): avoid uv venv pythonw launcher console flash on Windows**. closed without merge
  - maintainer direction: category `silent_close`; implication `active`
- **#53879: fix(windows): hide backend subprocess windows by default**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#54220`, `#54236`, `#53892`, `#54417`
- **#55613: fix(cli): add CREATE_BREAKAWAY_FROM_JOB to windows_hide_flags()**. closed without merge
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `0dbf639bc8`, `aa2ae36c3f`, `#70205`
- **#55667: fix(cli): add CREATE_BREAKAWAY_FROM_JOB to windows_hide_flags()**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#55613`, `#55604`
- **#55692: fix(cli): include breakaway flag for hidden Windows subprocesses**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#55613`, `#55604`, `#55667`
- **#55753: fix(windows): add CREATE_BREAKAWAY_FROM_JOB to windows_hide_flags()**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#55613`, `#55604`, `#55667`, `#55692`
- **#75224: fix: hide WhatsApp adapter helper subprocesses on Windows**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - files: `plugins/platforms/whatsapp/adapter.py`, `tests/gateway/test_whatsapp_adapter_hides_console_window.py`, `tests/gateway/test_whatsapp_connect.py`
- **#108309: fix(windows): add CREATE_BREAKAWAY_FROM_JOB to windows_hide_flags()**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - files: `hermes_cli/_subprocess_compat.py`, `tests/tools/test_windows_native_support.py`

## Fix status

- Merged to main in PR #54417 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-06-28`.
- Subsystem: `apps/desktop`
