{"repo":"NousResearch/hermes-agent","public_id":"53178","display_label":"Windows console flash and encoding fixes for git probes","summary":"These items address two related Windows-specific bugs: subprocess calls spawning visible console windows during git/shell operations, and a cp950 encoding crash on non-ASCII paths in the dashboard's git probe logic.","subsystem":"dashboard","counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":54236,"merged_at":"2026-06-28T12:28:46+00:00"},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"verified","last_verified_at":"2026-06-28T12:28:46+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":53016,"pr_number":54236,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-06-28T12:28:46+00:00"},{"issue_number":53053,"pr_number":54236,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-06-28T12:28:46+00:00"},{"issue_number":53178,"pr_number":54236,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-06-28T12:28:46+00:00"},{"issue_number":53282,"pr_number":54236,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-06-28T12:28:46+00:00"}],"members":[{"kind":"issue","number":53178,"title":"[Windows] dashboard Projects-tree git probe (tui_gateway/git_probe.py run_git) flashes a console window per repo + crashes on non-ASCII paths (missing CREATE_NO_WINDOW + encoding)","url":null,"state":"closed"},{"kind":"pr","number":53184,"title":"fix(windows): suppress git-probe console flash + cp950 crash in dashboard","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#54220","number":54220},{"kind":"pr","value":"#54236","number":54236},{"kind":"pr","value":"#53892","number":53892},{"kind":"pr","value":"#54417","number":54417}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":54236,"title":"fix(windows): hide console-window flash on backend git/gh/wmic/bash subprocess spawns","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["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":[{"file":"agent/coding_context.py","hunks":["@@ -60,6 +60,8 @@\n from pathlib import Path\n from typing import Any, Optional\n \n+from hermes_cli._subprocess_compat import IS_WINDOWS, windows_hide_flags\n+\n logger = logging.getLogger(\"hermes.coding_context\")\n \n CODING_TOOLSET = \"coding\"","@@ -647,12 +649,14 @@ def _enabled_mcp_servers(config: Optional[dict[str, Any]]) -> list[str]:\n \n \n def _git(cwd: Path, *args: str) -> str:\n+    _popen_kwargs = {\"creationflags\": windows_hide_flags()} if IS_WINDOWS else {}\n     try:\n         out = subprocess.run(\n             [\"git\", \"-C\", str(cwd), *args],\n             capture_output=True,\n             text=True,\n             timeout=_GIT_TIMEOUT,\n+            **_popen_kwargs,\n         )\n     except (OSError, subprocess.SubprocessError):\n         return \"\""]},{"file":"agent/context_references.py","hunks":["@@ -12,6 +12,7 @@\n from typing import Awaitable, Callable\n \n from agent.model_metadata import estimate_tokens_rough\n+from hermes_cli._subprocess_compat import IS_WINDOWS, windows_hide_flags\n \n _QUOTED_REFERENCE_VALUE = r'(?:`[^`\\n]+`|\"[^\"\\n]+\"|\\'[^\\'\\n]+\\')'\n REFERENCE_PATTERN = re.compile(","@@ -290,6 +291,7 @@ def _expand_git_reference(\n     args: list[str],\n     label: str,\n ) -> tuple[str | None, str | None]:\n+    _popen_kwargs = {\"creationflags\": windows_hide_flags()} if IS_WINDOWS else {}\n     try:\n         result = subprocess.run(\n             [\"git\", *args],","@@ -298,6 +300,7 @@ def _expand_git_reference(\n             text=True,\n             timeout=30,\n             stdin=subprocess.DEVNULL,\n+            **_popen_kwargs,\n         )\n     except subprocess.TimeoutExpired:\n         return f\"{ref.raw}: git command timed out (30s)\", None","@@ -483,6 +486,7 @@ def _iter_visible_entries(path: Path, cwd: Path, limit: int) -> list[Path]:\n \n \n def _rg_files(path: Path, cwd: Path, limit: int) -> list[Path] | None:\n+    _popen_kwargs = {\"creationflags\": windows_hide_flags()} if IS_WINDOWS else {}\n     try:\n         result = subprocess.run(\n             [\"rg\", \"--files\", str(path.relative_to(cwd))],","@@ -491,6 +495,7 @@ def _rg_files(path: Path, cwd: Path, limit: int) -> list[Path] | None:\n             text=True,\n             timeout=10,\n             stdin=subprocess.DEVNULL,\n+            **_popen_kwargs,\n         )\n     except (FileNotFoundError, OSError, subprocess.TimeoutExpired):\n         return None"]},{"file":"agent/shell_hooks.py","hunks":["@@ -122,6 +122,8 @@\n from pathlib import Path\n from typing import Any, Callable, Dict, Iterator, List, Optional, Set, Tuple\n \n+from hermes_cli._subprocess_compat import IS_WINDOWS, windows_hide_flags\n+\n try:\n     import fcntl  # POSIX only; Windows falls back to best-effort without flock.\n except ImportError:  # pragma: no cover","@@ -441,6 +443,7 @@ def _spawn(spec: ShellHookSpec, stdin_json: str) -> Dict[str, Any]:\n         return result\n \n     t0 = time.monotonic()\n+    _popen_kwargs = {\"creationflags\": windows_hide_flags()} if IS_WINDOWS else {}\n     try:\n         proc = subprocess.run(\n             argv,","@@ -449,6 +452,7 @@ def _spawn(spec: ShellHookSpec, stdin_json: str) -> Dict[str, Any]:\n             timeout=spec.timeout,\n             text=True,\n             shell=False,\n+            **_popen_kwargs,\n         )\n     except subprocess.TimeoutExpired:\n         result[\"timed_out\"] = True"]},{"file":"agent/skill_preprocessing.py","hunks":["@@ -5,6 +5,8 @@\n import subprocess\n from pathlib import Path\n \n+from hermes_cli._subprocess_compat import IS_WINDOWS, windows_hide_flags\n+\n logger = logging.getLogger(__name__)\n \n # Matches ${HERMES_SKILL_DIR} / ${HERMES_SESSION_ID} tokens in SKILL.md.","@@ -66,6 +68,7 @@ def run_inline_shell(command: str, cwd: Path | None, timeout: int) -> str:\n     Failures return a short ``[inline-shell error: ...]`` marker instead of\n     raising, so one bad snippet can't wreck the whole skill message.\n     \"\"\"\n+    _popen_kwargs = {\"creationflags\": windows_hide_flags()} if IS_WINDOWS else {}\n     try:\n         completed = subprocess.run(\n             [\"bash\", \"-c\", command],","@@ -75,6 +78,7 @@ def run_inline_shell(command: str, cwd: Path | None, timeout: int) -> str:\n             timeout=max(1, int(timeout)),\n             check=False,\n             stdin=subprocess.DEVNULL,\n+            **_popen_kwargs,\n         )\n     except subprocess.TimeoutExpired:\n         return f\"[inline-shell timeout after {timeout}s: {command}]\""]},{"file":"gateway/status.py","hunks":["@@ -81,12 +81,18 @@ def terminate_pid(pid: int, *, force: bool = False) -> None:\n     because os.kill(..., SIGTERM) is not equivalent to a tree-killing hard stop.\n     \"\"\"\n     if force and _IS_WINDOWS:\n+        # CREATE_NO_WINDOW: terminate_pid runs from the windowless pythonw.exe\n+        # gateway/desktop backend, so a bare taskkill spawn would flash a\n+        # conhost window on every force-kill.\n+        from hermes_cli._subprocess_compat import windows_hide_flags\n+\n         try:\n             result = subprocess.run(\n                 [\"taskkill\", \"/PID\", str(pid), \"/T\", \"/F\"],\n                 capture_output=True,\n                 text=True,\n                 timeout=10,\n+                creationflags=windows_hide_flags(),\n             )\n         except FileNotFoundError:\n             os.kill(pid, signal.SIGTERM)"]},{"file":"hermes_cli/copilot_auth.py","hunks":["@@ -27,6 +27,8 @@\n from pathlib import Path\n from typing import Optional\n \n+from hermes_cli._subprocess_compat import IS_WINDOWS, windows_hide_flags\n+\n logger = logging.getLogger(__name__)\n \n # OAuth device code flow constants (same client ID as opencode/Copilot CLI)","@@ -130,6 +132,7 @@ def _try_gh_cli_token() -> Optional[str]:\n     clean_env = {k: v for k, v in os.environ.items()\n                  if k not in {\"GITHUB_TOKEN\", \"GH_TOKEN\"}}\n \n+    _popen_kwargs = {\"creationflags\": windows_hide_flags()} if IS_WINDOWS else {}\n     for gh_path in _gh_cli_candidates():\n         cmd = [gh_path, \"auth\", \"token\"]\n         if hostname:","@@ -141,6 +144,7 @@ def _try_gh_cli_token() -> Optional[str]:\n                 text=True,\n                 timeout=5,\n                 env=clean_env,\n+                **_popen_kwargs,\n             )\n         except (FileNotFoundError, subprocess.TimeoutExpired) as exc:\n             logger.debug(\"gh CLI token lookup failed (%s): %s\", gh_path, exc)"]},{"file":"hermes_cli/gateway.py","hunks":["@@ -384,6 +384,12 @@ def _matches_gateway_runtime(command: str) -> bool:\n             # removed as part of the WMIC deprecation — fall back to\n             # PowerShell's Get-CimInstance.  Any OSError here (FileNotFoundError\n             # on missing wmic) trips the fallback.\n+            # Hide the console window: this scan runs inside the windowless\n+            # pythonw.exe gateway/desktop backend, so a bare wmic/powershell\n+            # spawn would flash a conhost window on every watchdog probe.\n+            from hermes_cli._subprocess_compat import windows_hide_flags\n+\n+            _no_window = {\"creationflags\": windows_hide_flags()}\n             wmic_path = shutil.which(\"wmic\")\n             used_fallback = False\n             result = None","@@ -402,6 +408,7 @@ def _matches_gateway_runtime(command: str) -> bool:\n                         encoding=\"utf-8\",\n                         errors=\"ignore\",\n                         timeout=10,\n+                        **_no_window,\n                     )\n                 except (OSError, subprocess.TimeoutExpired):\n                     result = None","@@ -427,6 +434,7 @@ def _matches_gateway_runtime(command: str) -> bool:\n                         encoding=\"utf-8\",\n                         errors=\"ignore\",\n                         timeout=15,\n+                        **_no_window,\n                     )\n                 except (OSError, subprocess.TimeoutExpired):\n                     return []"]},{"file":"hermes_cli/main.py","hunks":["@@ -5825,6 +5825,11 @@ def _find_stale_dashboard_pids(\n             # here is errors=\"ignore\": it prevents a reader-thread\n             # UnicodeDecodeError from leaving result.stdout=None and turning\n             # the later .split() into an AttributeError (#17049).\n+            # CREATE_NO_WINDOW hides the conhost flash: this scan can run from\n+            # the windowless pythonw.exe desktop/gateway backend during an\n+            # update, where a bare wmic spawn would pop a console window.\n+            from hermes_cli._subprocess_compat import windows_hide_flags\n+\n             result = subprocess.run(\n                 [\"wmic\", \"process\", \"get\", \"ProcessId,CommandLine\", \"/FORMAT:LIST\"],\n                 capture_output=True,","@@ -5832,6 +5837,7 @@ def _find_stale_dashboard_pids(\n                 timeout=10,\n                 encoding=\"utf-8\",\n                 errors=\"ignore\",\n+                creationflags=windows_hide_flags(),\n             )\n             if result.returncode != 0 or result.stdout is None:\n                 return []"]},{"file":"tests/gateway/test_status.py","hunks":["@@ -621,16 +621,22 @@ def test_force_uses_taskkill_on_windows(self, monkeypatch):\n         calls = []\n         monkeypatch.setattr(status, \"_IS_WINDOWS\", True)\n \n-        def fake_run(cmd, capture_output=False, text=False, timeout=None):\n-            calls.append((cmd, capture_output, text, timeout))\n+        def fake_run(cmd, capture_output=False, text=False, timeout=None, creationflags=0):\n+            calls.append((cmd, capture_output, text, timeout, creationflags))\n             return SimpleNamespace(returncode=0, stdout=\"\", stderr=\"\")\n \n         monkeypatch.setattr(status.subprocess, \"run\", fake_run)\n \n         status.terminate_pid(123, force=True)\n \n+        # taskkill is spawned with the no-window flag so the windowless\n+        # pythonw.exe backend doesn't flash a conhost window on force-kill.\n+        # windows_hide_flags() is 0 on the POSIX test host (a valid no-op\n+        # creationflags value); on real Windows it is CREATE_NO_WINDOW.\n+        from hermes_cli._subprocess_compat import windows_hide_flags\n+\n         assert calls == [\n-            ([\"taskkill\", \"/PID\", \"123\", \"/T\", \"/F\"], True, True, 10)\n+            ([\"taskkill\", \"/PID\", \"123\", \"/T\", \"/F\"], True, True, 10, windows_hide_flags())\n         ]\n \n     def test_force_falls_back_to_sigterm_when_taskkill_missing(self, monkeypatch):"]},{"file":"tests/test_windows_subprocess_no_window_flags.py","hunks":["@@ -0,0 +1,263 @@\n+from __future__ import annotations\n+\n+import subprocess\n+from pathlib import Path\n+from types import SimpleNamespace\n+\n+\n+_CREATE_NO_WINDOW = 0x08000000\n+\n+\n+class _Completed:\n+    def __init__(self, stdout: str | bytes = \"ok\\n\", returncode: int = 0):\n+        self.stdout = stdout\n+        self.stderr = \"\"\n+        self.returncode = returncode\n+\n+\n+def test_tui_gateway_git_probe_hides_git_windows(monkeypatch):\n+    from tui_gateway import git_probe\n+\n+    captured = []\n+\n+    def fake_run(cmd, **kwargs):\n+        captured.append((cmd, kwargs))\n+        return _Completed(stdout=\"main\\n\")\n+\n+    monkeypatch.setattr(git_probe, \"IS_WINDOWS\", True)\n+    monkeypatch.setattr(git_probe, \"windows_hide_flags\", lambda: _CREATE_NO_WINDOW)\n+    monkeypatch.setattr(git_probe.subprocess, \"run\", fake_run)\n+\n+    assert git_probe.run_git(\"C:/repo\", \"branch\", \"--show-current\") == \"main\"\n+\n+    assert captured == [\n+        (\n+            [\"git\", \"-C\", \"C:/repo\", \"branch\", \"--show-current\"],\n+            {\n+                \"capture_output\": True,\n+                \"text\": True,\n+                \"encoding\": \"utf-8\",\n+                \"errors\": \"replace\",\n+                \"timeout\": git_probe._GIT_TIMEOUT,\n+                \"check\": False,\n+                \"stdin\": subprocess.DEVNULL,\n+                \"creationflags\": _CREATE_NO_WINDOW,\n+            },\n+        )\n+    ]\n+\n+\n+def test_tui_gateway_fuzzy_file_listing_hides_git_windows(monkeypatch):\n+    from hermes_cli import _subprocess_compat\n+    from tui_gateway import server\n+\n+    captured = []\n+\n+    def fake_run(cmd, **kwargs):\n+        captured.append((cmd, kwargs))\n+        if cmd[-1] == \"--show-toplevel\":\n+            return _Completed(stdout=b\"C:/repo\\n\")\n+        return _Completed(stdout=b\"src/main.py\\0README.md\\0\")\n+\n+    monkeypatch.setattr(_subprocess_compat, \"IS_WINDOWS\", True)\n+    monkeypatch.setattr(_subprocess_compat, \"windows_hide_flags\", lambda: _CREATE_NO_WINDOW)\n+    monkeypatch.setattr(server.subprocess, \"run\", fake_run)\n+    server._fuzzy_cache.clear()\n+\n+    assert server._list_repo_files(\"C:/repo\") == [\"src/main.py\", \"README.md\"]\n+\n+    assert [kwargs[\"creationflags\"] for _, kwargs in captured] == [\n+        _CREATE_NO_WINDOW,\n+        _CREATE_NO_WINDOW,\n+    ]\n+\n+\n+def test_coding_context_git_hides_git_windows(monkeypatch):\n+    from agent import coding_context\n+\n+    captured = []\n+\n+    def fake_run(cmd, **kwargs):\n+        captured.append((cmd, kwargs))\n+        return _Completed(stdout=\"clean\\n\")\n+\n+    monkeypatch.setattr(coding_context, \"IS_WINDOWS\", True)\n+    monkeypatch.setattr(coding_context, \"windows_hide_flags\", lambda: _CREATE_NO_WINDOW)\n+    monkeypatch.setattr(coding_context.subprocess, \"run\", fake_run)\n+\n+    assert coding_context._git(Path(\"C:/repo\"), \"status\", \"--short\") == \"clean\"\n+    assert captured[0][1][\"creationflags\"] == _CREATE_NO_WINDOW\n+\n+\n+def test_context_reference_git_and_rg_hide_windows(monkeypatch):\n+    from agent import context_references\n+\n+    captured = []\n+\n+    def fake_run(cmd, **kwargs):\n+        captured.append((cmd, kwargs))\n+        if cmd[0] == \"rg\":\n+            return _Completed(stdout=\"src/main.py\\n\")\n+        return _Completed(stdout=\"diff --git a/src/main.py b/src/main.py\\n\")\n+\n+    monkeypatch.setattr(context_references, \"IS_WINDOWS\", True)\n+    monkeypatch.setattr(context_references, \"windows_hide_flags\", lambda: _CREATE_NO_WINDOW)\n+    monkeypatch.setattr(context_references.subprocess, \"run\", fake_run)\n+\n+    ref = context_references.ContextReference(\n+        raw=\"@diff\",\n+        kind=\"diff\",\n+        target=\"\",\n+        start=0,\n+        end=5,\n+    )\n+    warning, block = context_references._expand_git_reference(\n+        ref,\n+        Path(\"C:/repo\"),\n+        [\"diff\"],\n+        \"git diff\",\n+    )\n+    assert warning is None\n+    assert block is not None\n+    assert \"git diff\" in block\n+    assert context_references._rg_files(Path(\"C:/repo/src\"), Path(\"C:/repo\"), 10) == [\n+        Path(\"src/main.py\")\n+    ]\n+\n+    assert [kwargs[\"creationflags\"] for _, kwargs in captured] == [\n+        _CREATE_NO_WINDOW,\n+        _CREATE_NO_WINDOW,\n+    ]\n+\n+\n+def test_copilot_gh_cli_probe_hides_gh_windows(monkeypatch):\n+    from hermes_cli import copilot_auth\n+\n+    captured = []\n+\n+    def fake_run(cmd, **kwargs):\n+        captured.append((cmd, kwargs))\n+        return _Completed(stdout=\"gho_from_cli\\n\")\n+\n+    monkeypatch.setattr(copilot_auth, \"IS_WINDOWS\", True)\n+    monkeypatch.setattr(copilot_auth, \"windows_hide_flags\", lambda: _CREATE_NO_WINDOW)\n+    monkeypatch.setattr(copilot_auth, \"_gh_cli_candidates\", lambda: [\"gh\"])\n+    monkeypatch.setattr(copilot_auth.subprocess, \"run\", fake_run)\n+\n+    assert copilot_auth._try_gh_cli_token() == \"gho_from_cli\"\n+    assert captured[0][0] == [\"gh\", \"auth\", \"token\"]\n+    assert captured[0][1][\"creationflags\"] == _CREATE_NO_WINDOW\n+\n+\n+def test_gateway_pid_scan_hides_wmic_and_powershell_windows(monkeypatch):\n+    from hermes_cli import gateway\n+    from hermes_cli import _subprocess_compat\n+\n+    captured = []\n+\n+    def fake_run(cmd, **kwargs):\n+        captured.append((cmd, kwargs))\n+        if cmd[0] == \"wmic\":\n+            return _Completed(stdout=\"\", returncode=1)\n+        return _Completed(stdout=\"CommandLine=hermes gateway\\nProcessId=123\\n\")\n+\n+    monkeypatch.setattr(gateway, \"is_windows\", lambda: True)\n+    monkeypatch.setattr(gateway.shutil, \"which\", lambda name: name)\n+    monkeypatch.setattr(_subprocess_compat, \"IS_WINDOWS\", True)\n+    monkeypatch.setattr(_subprocess_compat, \"windows_hide_flags\", lambda: _CREATE_NO_WINDOW)\n+    monkeypatch.setattr(gateway.subprocess, \"run\", fake_run)\n+\n+    assert gateway._scan_gateway_pids(set()) == [123]\n+    assert [kwargs[\"creationflags\"] for _, kwargs in captured] == [\n+        _CREATE_NO_WINDOW,\n+        _CREATE_NO_WINDOW,\n+    ]\n+\n+\n+def test_stale_dashboard_windows_scan_hides_wmic(monkeypatch):\n+    from hermes_cli import main\n+    from hermes_cli import _subprocess_compat\n+\n+    captured = []\n+\n+    def fake_run(cmd, **kwargs):\n+        captured.append((cmd, kwargs))\n+        return _Completed(stdout=\"CommandLine=hermes dashboard\\nProcessId=123\\n\")\n+\n+    monkeypatch.setattr(main.sys, \"platform\", \"win32\")\n+    monkeypatch.setattr(_subprocess_compat, \"IS_WINDOWS\", True)\n+    monkeypatch.setattr(_subprocess_compat, \"windows_hide_flags\", lambda: _CREATE_NO_WINDOW)\n+    monkeypatch.setattr(main.subprocess, \"run\", fake_run)\n+\n+    assert main._find_stale_dashboard_pids() == [123]\n+    assert captured[0][1][\"creationflags\"] == _CREATE_NO_WINDOW\n+\n+\n+def test_gateway_force_kill_hides_taskkill_window(monkeypatch):\n+    from gateway import status\n+    from hermes_cli import _subprocess_compat\n+\n+    captured = []\n+\n+    def fake_run(cmd, **kwargs):\n+        captured.append((cmd, kwargs))\n+        return _Completed(stdout=\"\")\n+\n+    monkeypatch.setattr(status, \"_IS_WINDOWS\", True)\n+    monkeypatch.setattr(_subprocess_compat, \"IS_WINDOWS\", True)\n+    monkeypatch.setattr(_subprocess_compat, \"windows_hide_flags\", lambda: _CREATE_NO_WINDOW)\n+    monkeypatch.setattr(status.subprocess, \"run\", fake_run)\n+\n+    status.terminate_pid(123, force=True)\n+\n+    assert captured == [\n+        (\n+            [\"taskkill\", \"/PID\", \"123\", \"/T\", \"/F\"],\n+            {\n+                \"capture_output\": True,\n+                \"text\": True,\n+                \"timeout\": 10,\n+                \"creationflags\": _CREATE_NO_WINDOW,\n+            },\n+        )\n+    ]\n+\n+\n+def test_shell_hooks_hide_hook_command_windows(monkeypatch):\n+    from agent import shell_hooks\n+\n+    captured = []\n+\n+    def fake_run(cmd, **kwargs):\n+        captured.append((cmd, kwargs))\n+        return SimpleNamespace(returncode=0, stdout=\"{}\", stderr=\"\")\n+\n+    monkeypatch.setattr(shell_hooks, \"IS_WINDOWS\", True)\n+    monkeypatch.setattr(shell_hooks, \"windows_hide_flags\", lambda: _CREATE_NO_WINDOW)\n+    monkeypatch.setattr(shell_hooks.subprocess, \"run\", fake_run)\n+\n+    result = shell_hooks._spawn(\n+        shell_hooks.ShellHookSpec(event=\"post_tool_call\", command=\"hook-bin --flag\"),\n+        \"{}\",\n+    )\n+\n+    assert result[\"returncode\"] == 0\n+    assert captured[0][1][\"creationflags\"] == _CREATE_NO_WINDOW\n+\n+\n+def test_inline_skill_shell_hides_bash_window(monkeypatch):\n+    from agent import skill_preprocessing\n+\n+    captured = []\n+\n+    def fake_run(cmd, **kwargs):\n+        captured.append((cmd, kwargs))\n+        return SimpleNamespace(returncode=0, stdout=\"ok\\n\", stderr=\"\")\n+\n+    monkeypatch.setattr(skill_preprocessing, \"IS_WINDOWS\", True)\n+    monkeypatch.setattr(skill_preprocessing, \"windows_hide_flags\", lambda: _CREATE_NO_WINDOW)\n+    monkeypatch.setattr(skill_preprocessing.subprocess, \"run\", fake_run)\n+\n+    assert skill_preprocessing.run_inline_shell(\"echo ok\", cwd=None, timeout=5) == \"ok\"\n+    assert captured[0][0] == [\"bash\", \"-c\", \"echo ok\"]\n+    assert captured[0][1][\"creationflags\"] == _CREATE_NO_WINDOW"]},{"file":"tui_gateway/git_probe.py","hunks":["@@ -31,6 +31,8 @@\n from collections.abc import Iterable\n from concurrent.futures import ThreadPoolExecutor\n \n+from hermes_cli._subprocess_compat import IS_WINDOWS, windows_hide_flags\n+\n _GIT_TIMEOUT = 1.5\n _WARM_WORKERS = 8\n ","@@ -45,14 +47,18 @@ def run_git(cwd: str, *args: str) -> str:\n     \"\"\"``git -C <cwd> <args>`` → stripped stdout, or ``\"\"`` on any failure.\"\"\"\n     if not cwd:\n         return \"\"\n+    _popen_kwargs = {\"creationflags\": windows_hide_flags()} if IS_WINDOWS else {}\n     try:\n         result = subprocess.run(\n             [\"git\", \"-C\", cwd, *args],\n             capture_output=True,\n             text=True,\n+            encoding=\"utf-8\",\n+            errors=\"replace\",\n             timeout=_GIT_TIMEOUT,\n             check=False,\n             stdin=subprocess.DEVNULL,\n+            **_popen_kwargs,\n         )\n         return result.stdout.strip() if result.returncode == 0 else \"\"\n     except Exception:"]},{"file":"tui_gateway/server.py","hunks":["@@ -11668,6 +11668,9 @@ def _list_repo_files(root: str) -> list[str]:\n             return cached[1]\n \n     files: list[str] = []\n+    from hermes_cli._subprocess_compat import windows_hide_flags\n+\n+    _creationflags = windows_hide_flags()\n     try:\n         top_result = subprocess.run(\n             [\"git\", \"-C\", root, \"rev-parse\", \"--show-toplevel\"],","@@ -11675,6 +11678,7 @@ def _list_repo_files(root: str) -> list[str]:\n             timeout=2.0,\n             check=False,\n             stdin=subprocess.DEVNULL,\n+            creationflags=_creationflags,\n         )\n         if top_result.returncode == 0:\n             top = top_result.stdout.decode(\"utf-8\", \"replace\").strip()","@@ -11693,6 +11697,7 @@ def _list_repo_files(root: str) -> list[str]:\n                 timeout=2.0,\n                 check=False,\n                 stdin=subprocess.DEVNULL,\n+                creationflags=_creationflags,\n             )\n             if list_result.returncode == 0:\n                 for p in list_result.stdout.decode(\"utf-8\", \"replace\").split(\"\\0\"):\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}