Review follow-up: the comment in _bash_starts now says why stdin=DEVNULL matters
beyond the bounded cleanup — cygwin init's handle_to_fn/NtQueryObject stalls on a
pipe file object that has a read pending (the ACP host's stdin), which is why the
probe blew its timeout inside ACP hosts — and why stderr stays captured (the
Mandatory-ASLR remediation keys off bash's dofork:/child_copy: text).
_bash_starts used subprocess.run(timeout=15). On Windows run()'s post-timeout
cleanup is an unbounded communicate(): the probe's MSYS children (true/cat)
outlive the killed bash holding the captured pipe write ends, so the reader
join never returned and the ACP host wedged for minutes at the first terminal
tool (faulthandler dumps in the thread sit in _bash_starts → _communicate).
Route the probe through hermes_cli._subprocess_compat.bounded_probe_run — own
Popen (stdin=DEVNULL, hidden window), communicate(timeout), process-tree kill
and a 1 s bounded drain — so a probe that cannot finish fails fast and
_find_bash falls through to its last-resort candidate. A timeout is recorded as
the probe detail so the ASLR diagnostics still see it.
Supersedes #69083 (@fangliquanflq), whose bounded_captured_run design landed on
main as bounded_probe_run for the git probes (#68997); this is the surviving
_bash_starts call-site conversion from that PR.
Co-authored-by: fangliquanflq <fangliquan@qq.com>
The new standalone test file duplicated TestGitBashExternalProgramProbe's
harness; one assertion on the recorded kwargs covers the #78820 contract.
The sibling ASLR-probe assertion is dropped (unchanged code, already green
on main). Comment trimmed to the WHY.
`_bash_starts()` in tools/environments/local_gitbash_probe.py ran bash.exe
with capture_output=True but no stdin=, so the MSYS2 child inherited the
TUI gateway's stdin pipe. The MSYS2 runtime switches that shared pipe to
PIPE_NOWAIT; the gateway's next sys.stdin.readline() then fails with
ERROR_NO_DATA, which the CRT maps to OSError(EINVAL), and the gateway
exits with code 1 ("gateway exited") on the first terminal call.
The sibling probe _mandatory_aslr_enabled() already passes
stdin=subprocess.DEVNULL; this brings _bash_starts() in line with it.
Add tests/tools/test_gitbash_probe_stdin.py asserting both probes forward
stdin=DEVNULL to subprocess.run (fails on the pre-fix source).
Repo scanners (check_subprocess_stdin, check-windows-footguns --all) flagged 21 sites where
the r3 single-line collapses lost stdin=DEVNULL, encoding='utf-8'/errors='replace', the
'# windows-footgun: ok' same-line marker, or the getattr(os, 'geteuid') gate. Each guard is
restored at the call site (real portability/hang fixes, not suppressions).