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:
+4
-3
@@ -75,6 +75,7 @@ def _termux_api_app_installed() -> bool:
|
||||
text=True,
|
||||
timeout=5,
|
||||
check=False,
|
||||
stdin=subprocess.DEVNULL,
|
||||
)
|
||||
return "package:com.termux.api" in (result.stdout or "")
|
||||
except Exception:
|
||||
@@ -388,7 +389,7 @@ class TermuxAudioRecorder:
|
||||
"-c", str(CHANNELS),
|
||||
]
|
||||
try:
|
||||
subprocess.run(command, capture_output=True, text=True, timeout=15, check=True)
|
||||
subprocess.run(command, capture_output=True, text=True, timeout=15, check=True, stdin=subprocess.DEVNULL)
|
||||
except subprocess.CalledProcessError as e:
|
||||
details = (e.stderr or e.stdout or str(e)).strip()
|
||||
raise RuntimeError(f"Termux microphone start failed: {details}") from e
|
||||
@@ -405,7 +406,7 @@ class TermuxAudioRecorder:
|
||||
mic_cmd = _termux_microphone_command()
|
||||
if not mic_cmd:
|
||||
return
|
||||
subprocess.run([mic_cmd, "-q"], capture_output=True, text=True, timeout=15, check=False)
|
||||
subprocess.run([mic_cmd, "-q"], capture_output=True, text=True, timeout=15, check=False, stdin=subprocess.DEVNULL)
|
||||
|
||||
def stop(self) -> Optional[str]:
|
||||
with self._lock:
|
||||
@@ -1095,7 +1096,7 @@ def play_audio_file(file_path: str) -> bool:
|
||||
exe = shutil.which(cmd[0])
|
||||
if exe:
|
||||
try:
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)
|
||||
with _playback_lock:
|
||||
_active_playback = proc
|
||||
proc.wait(timeout=300)
|
||||
|
||||
Reference in New Issue
Block a user