feat(cli): add shell noise filtering and improve command execution with interactive login shell

This commit is contained in:
teknium1 2026-02-27 16:26:47 -08:00
parent f14ff3e041
commit fb7df099e0
2 changed files with 49 additions and 10 deletions

View file

@ -9,6 +9,23 @@ import time
from tools.environments.base import BaseEnvironment
# Noise lines emitted by interactive shells when stdin is not a terminal.
# Filtered from output to keep tool results clean.
_SHELL_NOISE = frozenset({
"bash: no job control in this shell",
"bash: no job control in this shell\n",
"no job control in this shell",
"no job control in this shell\n",
})
def _clean_shell_noise(output: str) -> str:
"""Strip shell startup warnings that leak when using -i without a TTY."""
lines = output.split("\n", 2) # only check first two lines
if lines and lines[0].strip() in _SHELL_NOISE:
return "\n".join(lines[1:])
return output
class LocalEnvironment(BaseEnvironment):
"""Run commands directly on the host machine.
@ -18,7 +35,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
- Uses interactive login shell so full user env is available
"""
def __init__(self, cwd: str = "", timeout: int = 60, env: dict = None):
@ -34,14 +51,15 @@ 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.
# 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 = os.environ.get("SHELL") or shutil.which("bash") or "/bin/bash"
proc = subprocess.Popen(
[user_shell, "-lc", exec_command],
[user_shell, "-lic", exec_command],
text=True,
cwd=work_dir,
env=os.environ | self.env,
@ -106,7 +124,8 @@ class LocalEnvironment(BaseEnvironment):
time.sleep(0.2)
reader.join(timeout=5)
return {"output": "".join(_output_chunks), "returncode": proc.returncode}
output = _clean_shell_noise("".join(_output_chunks))
return {"output": output, "returncode": proc.returncode}
except Exception as e:
return {"output": f"Execution error: {str(e)}", "returncode": 1}