fix: prevent TUI gateway stdin EOF crash across all TUI-context subprocess calls

When Hermes runs in TUI mode, the gateway child process communicates with
the Node.js parent over a JSON-RPC protocol on stdin. Subprocess calls that
inherit this stdin fd can trigger a race condition where the child's stdin
read returns EOF, causing the gateway to exit cleanly (exit code 0) mid-tool-
execution.

This is the same root cause as issue #14036 (byterover plugin) and PR #39257
(SSH environment backend). This commit applies the fix — stdin=subprocess.DEVNULL
— to all 85 subprocess.run() and subprocess.Popen() calls that execute inside
the TUI gateway child process.

Scope: TUI-context code only (agent/, tools/, plugins/, tui_gateway/server.py).
CLI code (cli.py, hermes_cli/), tests, scripts, and gateway process management
are excluded — they don't run inside the TUI child and inherit the terminal's
stdin, not the JSON-RPC pipe.

85 call sites across 28 files. All files pass syntax check.
This commit is contained in:
m4dni5
2026-06-08 22:46:57 -07:00
committed by Teknium
parent 54318c65b0
commit d1f23bb2d5
27 changed files with 84 additions and 20 deletions
+4 -1
View File
@@ -46,6 +46,7 @@ def _ensure_singularity_available() -> str:
try:
result = subprocess.run(
[exe, "version"], capture_output=True, text=True, timeout=10,
stdin=subprocess.DEVNULL,
)
except FileNotFoundError:
raise RuntimeError(
@@ -136,6 +137,7 @@ def _get_or_build_sif(image: str, executable: str = "apptainer") -> str:
result = subprocess.run(
[executable, "build", str(sif_path), image],
capture_output=True, text=True, timeout=600, env=env,
stdin=subprocess.DEVNULL,
)
if result.returncode != 0:
logger.warning("SIF build failed, falling back to docker:// URL")
@@ -218,7 +220,7 @@ class SingularityEnvironment(BaseEnvironment):
cmd.extend([str(self.image), self.instance_id])
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120, stdin=subprocess.DEVNULL)
if result.returncode != 0:
raise RuntimeError(f"Failed to start instance: {result.stderr}")
self._instance_started = True
@@ -250,6 +252,7 @@ class SingularityEnvironment(BaseEnvironment):
subprocess.run(
[self.executable, "instance", "stop", self.instance_id],
capture_output=True, text=True, timeout=30,
stdin=subprocess.DEVNULL,
)
logger.info("Singularity instance %s stopped", self.instance_id)
except Exception as e: