fix(tools): stop the Git Bash probe from inheriting host stdin - #74241
fix(tools): stop the Git Bash probe from inheriting host stdin#74241lxman wants to merge 1 commit into
Conversation
|
Thanks for the focused fix. Current The PR regression test directly covers the Git Bash probe, and a read-only Automated hermes-sweeper review. |
SummaryOne PR directly addresses #73693. #74241 prevents the Git Bash and Mandatory-ASLR probes from inheriting the host JSON-RPC stdin pipe, targeting the identified cause of the Windows JetBrains ACP hang rather than its timeout symptoms. Related pull requests
Suggested consolidationKeep #74241 open with a salvage path: retain the explicit DEVNULL stdin isolation for both probe subprocesses and the Git Bash regression test, because these changes directly address the independently reproduced pipe-inheritance mechanism. There are no competing PRs or duplicates to close. Complex graphflowchart LR
classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
classDef best stroke-width:3px,stroke:#b45309
classDef target stroke-width:3px,stroke:#4338ca
I73693(["issue #73693 (open)"])
P74241["PR #74241 (open)"]
P74241 -->|best fix| I73693
class I73693 open
class P74241 open
class P74241 best
class P74241 target
click I73693 "https://github.com/NousResearch/hermes-agent/issues/73693"
click P74241 "https://github.com/NousResearch/hermes-agent/pull/74241"
Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label). Cross-PR triage: Reviewed 1 pull request and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 3 kB of PR diffs, 8 kB of issue/PR text, 3 kB of discussion (1 comments), 2 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch. |
`_bash_starts` and `_mandatory_aslr_enabled` spawned children without an explicit `stdin=`, so both inherited fd 0 from the Hermes process. Under a JSON-RPC stdio host that fd is the protocol pipe. This is the same class as NousResearch#14036/NousResearch#39257 (TUI gateway), but it reproduces under the ACP adapter as a hang rather than an EOF exit: 1. MSYS bash blocks on the inherited pipe during startup. 2. The `timeout=15` fires, but on Windows `git`/`bash` resolve to a `Git\bin` wrapper that spawns the real mingw binary; killing the wrapper leaves the grandchild alive, still holding the pipe. 3. `subprocess.run`'s post-kill `communicate()` takes no timeout, so the calling thread blocks forever — and with it the whole agent turn. The visible symptom is NousResearch#73693: `read_file` and `terminal` hang forever in JetBrains, because a plain local read initialises `LocalEnvironment`, which runs this probe. The same operations work from the CLI, where fd 0 is a console rather than a pipe. `scripts/check_subprocess_stdin.py` exists to prevent exactly this, but its detection is line-based and requires an argument character right after the open paren, so calls formatted as `subprocess.run(` + newline are never examined. Both call sites here are formatted that way. Fixes NousResearch#73693 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
43c8293 to
752bbad
Compare
What & why
read_fileandterminalhang forever when Hermes runs as an ACP agent in aJetBrains IDE on Windows (#73693). A plain local read builds
ShellFileOperations,which initialises
LocalEnvironmentand runs the Git Bash health probe._bash_startsand_mandatory_aslr_enabledspawned children without an explicitstdin=, so both inherited fd 0. Under a JSON-RPC stdio host that fd is theprotocol pipe — the same hazard as #14036/#39257 in the TUI gateway, but it
presents as a hang instead of an EOF exit:
timeout=15fires, but on Windowsbashresolves to aGit\binwrapper thatspawns the real mingw binary. Killing the wrapper leaves the grandchild alive,
still holding the pipe.
subprocess.run's post-killcommunicate()takes no timeout, so the callingthread blocks forever — and with it the agent turn.
That third step is why the existing timeout doesn't save us, and why the same
call is harmless from the CLI, where fd 0 is a console.
Note for maintainers: the guard that should have caught this
scripts/check_subprocess_stdin.pyexists to prevent exactly this class. Itmisses both call sites because detection is line-based and the regex requires an
argument character immediately after the open paren:
r"subprocess\.(run|Popen|call|check_output|check_call)\s*\([\"'a-zA-Z_\[\(]"So any call formatted as
subprocess.run(+ newline — the standard formatting forlong calls — is never examined. A scan for that shape across the TUI-context dirs
finds 29 calls the guard currently cannot see, including both fixed here.
I've left the guard alone to keep this PR focused; happy to send that as a
follow-up (tightening it turns those 29 red, so it wants its own review).
Note
scripts/check_subprocess_stdin.pyalso crashes on Windows before printingresults (
UnicodeEncodeErroron the ❌ emoji under cp1252) — already fixed in #42775.How to test
Regression test added to
TestGitBashExternalProgramProbe; it asserts the probepasses
stdin=subprocess.DEVNULL.Manual, on Windows + JetBrains:
pwd && lsthroughterminal.Platforms
Verified on Windows 11, Python 3.11.14, Hermes 0.19.0, DataGrip 2026.2.1 (ACP v1).
POSIX is unaffected in practice — native git/bash don't block on an inherited
pipe at startup,
SIGKILLreaps the direct child (no wrapper/grandchild split),and CPython's POSIX timeout path doesn't do an unbounded post-kill drain. The
change is still correct there: it stops children sharing the protocol fd.
Two pre-existing failures in
tests/tools/test_find_shell.py(
TestFindShellPrefersUserShell) reproduce on an unmodified checkout on Windowsand are unrelated.
Fixes #73693