diff --git a/tools/environments/local.py b/tools/environments/local.py index 945e3349ff..e1df97b4cd 100644 --- a/tools/environments/local.py +++ b/tools/environments/local.py @@ -17,19 +17,19 @@ from tools.environments.base import BaseEnvironment _OUTPUT_FENCE = "__HERMES_FENCE_a9f7b3__" -def _find_shell() -> str: - """Find the best shell for command execution. +def _find_bash() -> str: + """Find bash for command execution. - On Unix: uses $SHELL, falls back to bash. + The fence wrapper uses bash syntax (semicolons, $?, printf), so we + must use bash — not the user's $SHELL which could be fish/zsh/etc. On Windows: uses Git Bash (bundled with Git for Windows). - Raises RuntimeError if no suitable shell is found on Windows. """ if not _IS_WINDOWS: return ( - os.environ.get("SHELL") - or shutil.which("bash") + shutil.which("bash") or ("/usr/bin/bash" if os.path.isfile("/usr/bin/bash") else None) or ("/bin/bash" if os.path.isfile("/bin/bash") else None) + or os.environ.get("SHELL") # last resort: whatever they have or "/bin/sh" ) @@ -59,6 +59,11 @@ def _find_shell() -> str: "Or set HERMES_GIT_BASH_PATH to your bash.exe location." ) + +# Backward compat — process_registry.py imports this name +_find_shell = _find_bash + + # Noise lines emitted by interactive shells when stdin is not a terminal. # Used as a fallback when output fence markers are missing. _SHELL_NOISE_SUBSTRINGS = ( @@ -159,13 +164,11 @@ class LocalEnvironment(BaseEnvironment): exec_command = self._prepare_command(command) try: - # Use the user's shell as an interactive login shell (-lic) so - # that ALL rc files are sourced — including content after the - # interactive guard in .bashrc (case $- in *i*)..esac) where - # tools like nvm, pyenv, and cargo install their init scripts. - # -l alone isn't enough: .profile sources .bashrc, but the guard - # returns early because the shell isn't interactive. - user_shell = _find_shell() + # The fence wrapper uses bash syntax (semicolons, $?, printf). + # Always use bash for the wrapper — NOT $SHELL which could be + # fish, zsh, or another shell with incompatible syntax. + # The -lic flags source rc files so tools like nvm/pyenv work. + user_shell = _find_bash() # Wrap with output fences so we can later extract the real # command output and discard shell init/exit noise. fenced_cmd = (