From 714d7cc1a4695a062a792b85647deb1d5ec4d457 Mon Sep 17 00:00:00 2001 From: Basil Al Shukaili Date: Thu, 2 Jul 2026 11:21:14 +0400 Subject: [PATCH] fix(windows): hide console flashes in GUI-reachable exec paths and provider transports (#56747) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six spawn sites reachable from the desktop GUI / TUI gateway lacked CREATE_NO_WINDOW, so a windowless parent (pythonw/Electron) flashed a conhost per spawn: cli.exec RPC, quick-commands exec dispatch, and shell.exec RPC in tui_gateway/server.py; the CLI REPL quick-commands exec in cli.py; and the per-session provider transports in agent/copilot_acp_client.py and agent/transports/codex_app_server.py (Popen, hide-only so PIPE stdio stays intact). All use hermes_cli._subprocess_compat.windows_hide_flags() (no-op on POSIX), matching the pattern already used at three other sites in tui_gateway/server.py. Deliberately hide-only — no detach flags, no Electron changes (per the #54220 revert history). Co-Authored-By: Claude Fable 5 --- agent/copilot_acp_client.py | 5 +++++ agent/transports/codex_app_server.py | 5 +++++ cli.py | 5 ++++- tui_gateway/server.py | 11 +++++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/agent/copilot_acp_client.py b/agent/copilot_acp_client.py index 5e095af3902b..662facc2dfee 100644 --- a/agent/copilot_acp_client.py +++ b/agent/copilot_acp_client.py @@ -503,6 +503,10 @@ class CopilotACPClient: def _run_prompt(self, prompt_text: str, *, timeout_seconds: float) -> tuple[str, str]: try: + # Hide the console the CLI child would otherwise flash on Windows + # (#56747). Hide-only — stdio pipes stay intact for the ACP wire. + from hermes_cli._subprocess_compat import windows_hide_flags + proc = subprocess.Popen( [self._acp_command] + self._acp_args, stdin=subprocess.PIPE, @@ -512,6 +516,7 @@ class CopilotACPClient: bufsize=1, cwd=self._acp_cwd, env=_build_subprocess_env(), + creationflags=windows_hide_flags(), ) except FileNotFoundError as exc: raise RuntimeError( diff --git a/agent/transports/codex_app_server.py b/agent/transports/codex_app_server.py index 273e44667d6a..7f5831f2a3ea 100644 --- a/agent/transports/codex_app_server.py +++ b/agent/transports/codex_app_server.py @@ -127,6 +127,10 @@ class CodexAppServerClient: # Codex emits tracing to stderr; default WARN keeps it quiet for users. spawn_env.setdefault("RUST_LOG", "warn") + # Hide the console the codex child would otherwise flash on Windows + # (#56747). Hide-only — stdio pipes stay intact for the app-server wire. + from hermes_cli._subprocess_compat import windows_hide_flags + self._proc = subprocess.Popen( cmd, stdin=subprocess.PIPE, @@ -134,6 +138,7 @@ class CodexAppServerClient: stderr=subprocess.PIPE, bufsize=0, env=spawn_env, + creationflags=windows_hide_flags(), ) self._next_id = 1 self._pending: dict[int, _Pending] = {} diff --git a/cli.py b/cli.py index 503acaf7d378..125fed613064 100644 --- a/cli.py +++ b/cli.py @@ -9481,9 +9481,12 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin): # has all API keys in os.environ. from tools.environments.local import _sanitize_subprocess_env sanitized_env = _sanitize_subprocess_env(os.environ.copy()) + from hermes_cli._subprocess_compat import windows_hide_flags result = subprocess.run( exec_cmd, shell=True, capture_output=True, - text=True, timeout=30, env=sanitized_env + text=True, timeout=30, env=sanitized_env, + # No console flash on Windows (#56747). + creationflags=windows_hide_flags(), ) output = result.stdout.strip() or result.stderr.strip() if output: diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 96e7a9447693..a35dd6394b70 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -13841,6 +13841,10 @@ def _(rid, params: dict) -> dict: if hint: return _ok(rid, {"blocked": True, "hint": hint, "code": -1, "output": ""}) try: + # CREATE_NO_WINDOW on Windows — under the desktop GUI's windowless + # parent, this spawn otherwise flashes a console (#56747). + from hermes_cli._subprocess_compat import windows_hide_flags + r = subprocess.run( [sys.executable, "-m", "hermes_cli.main", *argv], capture_output=True, @@ -13851,6 +13855,7 @@ def _(rid, params: dict) -> dict: # needs provider credentials. Tier-1 secrets still stripped (#29157). env=hermes_subprocess_env(inherit_credentials=True), stdin=subprocess.DEVNULL, + creationflags=windows_hide_flags(), ) parts = [r.stdout or "", r.stderr or ""] out = "\n".join(p for p in parts if p).strip() or "(no output)" @@ -13910,6 +13915,8 @@ def _(rid, params: dict) -> dict: # has all API keys in os.environ. from tools.environments.local import _sanitize_subprocess_env sanitized_env = _sanitize_subprocess_env(os.environ.copy()) + from hermes_cli._subprocess_compat import windows_hide_flags + r = subprocess.run( qc.get("command", ""), shell=True, @@ -13918,6 +13925,7 @@ def _(rid, params: dict) -> dict: timeout=30, stdin=subprocess.DEVNULL, env=sanitized_env, + creationflags=windows_hide_flags(), ) output = ( (r.stdout or "") @@ -16984,9 +16992,12 @@ def _(rid, params: dict) -> dict: except ImportError: return _err(rid, 5001, "shell.exec unavailable: approval safety module not importable") try: + from hermes_cli._subprocess_compat import windows_hide_flags + r = subprocess.run( cmd, shell=True, capture_output=True, text=True, timeout=30, cwd=os.getcwd(), stdin=subprocess.DEVNULL, + creationflags=windows_hide_flags(), ) return _ok( rid,