{"repo":"NousResearch/hermes-agent","public_id":"14638","display_label":"Windows terminal output and Git Bash execution issues","summary":"Multiple bugs and fixes related to terminal command execution on Windows, specifically around empty output, exit code 126, select.select() incompatibility with Windows pipe file descriptors, and Git Bash path/environment handling.","subsystem":"environments","counts":{"members":7,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":4,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":21259},"resolution":{"state":"partially_resolved","by":"pr-21259"},"freshness":{"state":"verified","last_verified_at":"2026-05-07T13:11:01+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.21142857142857144},"fix_edges":[{"issue_number":14638,"pr_number":21259,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":14638,"title":"Windows: Terminal exits 126 with empty output on every command (Git Bash backend)","url":null,"state":"closed"},{"kind":"issue","number":16425,"title":"[Bug]: Windows local terminal commands can complete without returning any output","url":null,"state":"closed"},{"kind":"issue","number":16426,"title":"[Windows] Terminal tool returns empty output on native Windows","url":null,"state":"closed"},{"kind":"issue","number":25514,"title":"# [Windows] Terminal foreground commands return empty output (select.select() fails on pipe fds)","url":null,"state":"closed"},{"kind":"pr","number":14644,"title":"fix(windows): native Git Bash terminal — exit 126 with empty output","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#12795","number":12795},{"kind":"pr","value":"#13397","number":13397}],"category":"superseded_by_pr","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":19258,"title":"fix(windows): terminal drain and cwd path conversion","url":null,"state":"closed","close_reason":{"category":"silent_close","implication":"active"},"merged":false},{"kind":"pr","number":21259,"title":"fix(windows): terminal drain + cwd path conversion (salvage #19258)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["tools/environments/base.py","tools/environments/local.py"],"fix_hunks":[{"file":"tools/environments/base.py","hunks":["@@ -489,6 +489,26 @@ def _wait_for_process(self, proc: ProcessHandle, timeout: int = 120) -> dict:\n \n         def _drain():\n             fd = proc.stdout.fileno()\n+            # select.select does NOT work on pipe fds on Windows (only sockets).\n+            # Use blocking os.read in a daemon thread instead — safe because\n+            # EOF arrives promptly when bash exits.\n+            if os.name == \"nt\":\n+                try:\n+                    while True:\n+                        chunk = os.read(fd, 4096)\n+                        if not chunk:\n+                            break\n+                        output_chunks.append(decoder.decode(chunk))\n+                except (ValueError, OSError):\n+                    pass\n+                finally:\n+                    try:\n+                        tail = decoder.decode(b\"\", final=True)\n+                        if tail:\n+                            output_chunks.append(tail)\n+                    except Exception:\n+                        pass\n+                return\n             idle_after_exit = 0\n             try:\n                 while True:"]},{"file":"tools/environments/local.py","hunks":["@@ -3,6 +3,7 @@\n import logging\n import os\n import platform\n+import re\n import shutil\n import signal\n import subprocess","@@ -403,6 +404,12 @@ def _run_bash(self, cmd_string: str, *, login: bool = False,\n             )\n             self.cwd = safe_cwd\n \n+        # On Windows, self.cwd may be a Git Bash-style path (/c/Users/...)\n+        # from pwd output. subprocess.Popen needs a native Windows path.\n+        _popen_cwd = self.cwd\n+        if _IS_WINDOWS and _popen_cwd and re.match(r'^/[a-zA-Z]/', _popen_cwd):\n+            _popen_cwd = _popen_cwd[1].upper() + ':' + _popen_cwd[2:].replace('/', '\\\\')\n+\n         proc = subprocess.Popen(\n             args,\n             text=True,","@@ -413,7 +420,7 @@ def _run_bash(self, cmd_string: str, *, login: bool = False,\n             stderr=subprocess.STDOUT,\n             stdin=subprocess.PIPE if stdin_data is not None else subprocess.DEVNULL,\n             preexec_fn=None if _IS_WINDOWS else os.setsid,\n-            cwd=self.cwd,\n+            cwd=_popen_cwd,\n         )\n         if not _IS_WINDOWS:\n             try:\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}