diff --git a/tools/environments/local.py b/tools/environments/local.py index f0041e8bd..428d31294 100644 --- a/tools/environments/local.py +++ b/tools/environments/local.py @@ -1,6 +1,7 @@ """Local execution environment with interrupt support and non-blocking I/O.""" import os +import shutil import signal import subprocess import threading @@ -17,6 +18,7 @@ class LocalEnvironment(BaseEnvironment): - Background stdout drain thread to prevent pipe buffer deadlocks - stdin_data support for piping content (bypasses ARG_MAX limits) - sudo -S transform via SUDO_PASSWORD env var + - Uses bash login shell so user env (.profile/.bashrc) is available """ def __init__(self, cwd: str = "", timeout: int = 60, env: dict = None): @@ -32,9 +34,14 @@ class LocalEnvironment(BaseEnvironment): exec_command = self._prepare_command(command) try: + # Use the user's login shell so that rc files (.profile, .bashrc, + # .zprofile, .zshrc, etc.) are sourced and user-installed tools + # (nvm, pyenv, cargo, etc.) are available. Without this, Python's + # Popen(shell=True) uses /bin/sh which is dash on Debian/Ubuntu + # and old bash on macOS — neither sources the user's environment. + user_shell = os.environ.get("SHELL") or shutil.which("bash") or "/bin/bash" proc = subprocess.Popen( - exec_command, - shell=True, + [user_shell, "-lc", exec_command], text=True, cwd=work_dir, env=os.environ | self.env, diff --git a/tools/process_registry.py b/tools/process_registry.py index 58bc788a3..230afd19c 100644 --- a/tools/process_registry.py +++ b/tools/process_registry.py @@ -32,6 +32,7 @@ Usage: import json import logging import os +import shutil import signal import subprocess import threading @@ -127,8 +128,9 @@ class ProcessRegistry: # Try PTY mode for interactive CLI tools try: import ptyprocess + user_shell = os.environ.get("SHELL") or shutil.which("bash") or "/bin/bash" pty_proc = ptyprocess.PtyProcess.spawn( - ["bash", "-c", command], + [user_shell, "-lc", command], cwd=session.cwd, env=os.environ | (env_vars or {}), dimensions=(30, 120), @@ -160,9 +162,11 @@ class ProcessRegistry: logger.warning("PTY spawn failed (%s), falling back to pipe mode", e) # Standard Popen path (non-PTY or PTY fallback) + # Use the user's login shell for consistency with LocalEnvironment -- + # ensures rc files are sourced and user tools are available. + user_shell = os.environ.get("SHELL") or shutil.which("bash") or "/bin/bash" proc = subprocess.Popen( - command, - shell=True, + [user_shell, "-lc", command], text=True, cwd=session.cwd, env=os.environ | (env_vars or {}),