Skip to content

fix(windows): exclude the WSL launcher stub from bash candidates - #103502

Open
Zhou1317fe5 wants to merge 1 commit into
NousResearch:mainfrom
Zhou1317fe5:fix/exclude-wsl-bash-stub
Open

Zhou1317fe5 wants to merge 1 commit into
NousResearch:mainfrom
Zhou1317fe5:fix/exclude-wsl-bash-stub

Conversation

@Zhou1317fe5

Copy link
Copy Markdown

When the Git Bash probe fails under cold-start contention, _find_bash()
falls through to shutil.which("bash"), which on WSL co-install machines
resolves to the System32 launcher stub. That stub passes _bash_starts
(it is a working bash inside WSL), but /c/... and /d/... do not exist
there — the session snapshot then runs under the WSL filesystem view and
every command fails with ENOENT while the terminal tool burns its whole
executor timeout (#103398). Live capture during a failing window:
System32�ash.exe + wsl.exe + wslhost.exe spawned together, and the
error signature is byte-identical when running the same snapshot commands
under C:\Windows\System32�ash.exe.

What this PR does

Exclude the stub from _windows_bash_candidates()'s shutil.which("bash")
fallback — resolved via %SystemRoot% (fallback %WINDIR%) so non-C:\
Windows installs are covered, normcase on both sides — and let the
existing structured "Git Bash not found" error surface instead. When the
Git Bash probe passes, behavior is unchanged.

Notes

Environment: Windows 10 19045 x64, Git for Windows 2.49.0, WSL Ubuntu-24.04
co-installed. Full ACP wire logs and process captures available.

When the Git Bash probe fails under cold-start contention, _find_bash()
falls through to shutil.which("bash"), which on WSL co-install machines
resolves to the System32 launcher stub. That stub passes _bash_starts
(it is a working bash *inside WSL*), but /c/... and /d/... do not exist
there — the session snapshot then runs under the WSL filesystem view and
every command fails with ENOENT while the terminal tool burns its whole
executor timeout (NousResearch#103398).

Exclude the stub — resolved via %SystemRoot% (fallback %WINDIR%),
normcase-compared so non-C:\ Windows installs are covered too — and let
the existing structured "Git Bash not found" error surface instead.
When the Git Bash probe passes, behavior is unchanged.

Refs NousResearch#103398, NousResearch#74982. Complements NousResearch#83413 (probe stdin) and NousResearch#103402
(probe kill-tree).
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists tool/terminal Terminal execution and process management backend/local Local shell execution platform/windows Native Windows-specific behavior or breakage sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows labels Sep 5, 2026
@alt-glitch

Copy link
Copy Markdown

This was generated by AI during triage.

Related: #41456 (rejects the same System32 WSL launcher when supplied via HERMES_GIT_BASH_PATH) — this PR covers the shutil.which("bash") fallback path instead, so the two are complementary rather than competing. Also related to the probe-hang fixes #83413 / #103402 and the reports #103398 / #74982.

@sunnya01

Copy link
Copy Markdown

Independent field confirmation from a third host, plus the trigger I could isolate — this PR looks like the right fix.

Host: Windows 11 (10.0.26200), git-installed Hermes v0.21.2 (2026.9.11, upstream a6c42780), Git for Windows 2.55.0 at D:\Git, WSL co-installed (C:\WINDOWS\system32\bash.exe precedes everything in the machine PATH).

Note this is not a missing-configuration case: Git Bash was correctly configured the whole time — HERMES_GIT_BASH_PATH=D:\Git\bin\bash.exe, and that binary runs fine:

D:\Git\bin\bash.exe --noprofile --norc -c "/usr/bin/true; /usr/bin/cat --version >/dev/null"  ->  rc 0

Observed (every terminal call, for the whole life of the backend process):

mktemp: failed to create file via template '/c/Users/<user>/AppData/Local/hermes/cache/terminal/hermes-snap-<id>.sh.tmp.XXXXXXXXXX': No such file or directory
/bin/bash: line 4: cd: /e/<workdir>: No such file or directory     ->  exit_code 126

34 occurrences in one day's logs/agent.log, each preceded by:

WARNING tools.environments.local: HERMES_GIT_BASH_PATH=D:\Git\bin\bash.exe fails to start; using C:\WINDOWS\system32\bash.EXE instead

Trigger: the 15 s probe timeout is a false negative when the MSYS image's first exec is slow. On this host (corporate endpoint-security agent + Defender) the first exec of D:\Git\bin\bash.exe from a fresh parent sporadically takes ~30 s, then ~0.3 s thereafter:

probe command, fresh parent:  first exec 30.76 s (rc 0)   -> immediately after: 0.65 s, 0.31 s, 0.33 s (rc 0)

So _bash_starts() (>15 s ⇒ False, cached per path in _bash_starts_cache for the process lifetime) condemns a healthy Git Bash for the whole session, while the WSL stub passes in 0.3–0.6 s because /usr/bin/true exists inside the distro. There is no hang here — subprocess.run returned normally, unlike #74982/#103398 — which is why the false-negative path warrants the same treatment as the hang path. Note also that ASLR is not a factor here ((Get-ProcessMitigation -System).Aslr.ForceRelocateImages = NOTSET), so this is purely the probe's 15 s budget vs. cold-start contention.

Verified locally with a one-line variant behaviourally identical to this PR (drop the shutil.which("bash") result when it lives under SystemRoot\System32 / WindowsApps):

def _is_wsl_bash(path: str) -> bool:
    lowered = path.replace("/", "\\").lower()
    return lowered.endswith("\\bash.exe") and ("\\system32\\" in lowered or "\\windowsapps\\" in lowered)

found = shutil.which("bash")
if found and not _is_wsl_bash(found) and found not in candidates:
    candidates.append(found)
  • Simulate the live failure (git-bash probe -> False, WSL probe -> True): before ⇒ _find_bash() = C:\WINDOWS\system32\bash.EXE (reproduces the report); after ⇒ D:\Git\bin\bash.exe.
  • E2E in a deliberately hostile env (no HERMES_GIT_BASH_PATH, stale PATH): hermes -z "Use the terminal tool once to run: echo OK; uname -s; ls -d /c/Users ." → OK / MINGW64_NT-10.0-26200 / /c/Users ✓ (was resolving to the WSL Linux bash before the change).

One note on the fallback after excluding the stub: on hosts with neither %LOCALAPPDATA%\hermes\git nor a Program Files\Git install, excluding the stub leaves candidates == [], so the structured "Git Bash not found" error fires. That is strictly better than silent WSL degradation, and it is easy to recover from — pointing %LOCALAPPDATA%\hermes\git at an existing Git install via a directory junction needs no admin rights, and bin\bash.exe still resolves its MSYS root correctly through the junction (verified: uname -s = MINGW64_NT-10.0-26200, /c/... paths resolve). Worth a line in the PR description if you want the recovery path documented.

Happy to run a build with this PR applied on this exact host (Windows 11 + WSL + a probe that intermittently exceeds 15 s) if a live confirmation would help.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend/local Local shell execution P2 Medium — degraded but workaround exists platform/windows Native Windows-specific behavior or breakage sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows tool/terminal Terminal execution and process management type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants