From 576fe0906a4d91c45f84c38d3af3928af4133071 Mon Sep 17 00:00:00 2001 From: dannyward630 Date: Fri, 19 Jun 2026 01:00:32 +0200 Subject: [PATCH 1/2] fix(env): run local commands through bash --- src/minisweagent/environments/local.py | 11 +++++++---- tests/environments/test_local.py | 11 +++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/minisweagent/environments/local.py b/src/minisweagent/environments/local.py index 2b12d33ec..aff1eed92 100644 --- a/src/minisweagent/environments/local.py +++ b/src/minisweagent/environments/local.py @@ -14,6 +14,8 @@ class LocalEnvironmentConfig(BaseModel): cwd: str = "" env: dict[str, str] = {} timeout: int = 30 + interpreter: list[str] = ["bash", "-lc"] + """Interpreter to use to execute commands. The command is appended as the final argument.""" class LocalEnvironment: @@ -26,7 +28,7 @@ def execute(self, action: dict, cwd: str = "", *, timeout: int | None = None) -> command = action.get("command", "") cwd = cwd or self.config.cwd or os.getcwd() try: - result = _run(command, cwd, os.environ | self.config.env, timeout or self.config.timeout) + result = _run(command, cwd, os.environ | self.config.env, timeout or self.config.timeout, self.config.interpreter) output = {"output": result.stdout, "returncode": result.returncode, "exception_info": ""} except Exception as e: raw_output = getattr(e, "output", None) @@ -69,11 +71,12 @@ def serialize(self) -> dict: } -def _run(command: str, cwd: str, env: dict[str, str], timeout: int) -> subprocess.CompletedProcess[str]: +def _run( + command: str, cwd: str, env: dict[str, str], timeout: int, interpreter: list[str] +) -> subprocess.CompletedProcess[str]: """Like subprocess.run, but kills the whole process group on timeout so no children are orphaned.""" process = subprocess.Popen( - command, - shell=True, + [*interpreter, command], text=True, cwd=cwd, env=env, diff --git a/tests/environments/test_local.py b/tests/environments/test_local.py index 705c19a20..a9b83f7cc 100644 --- a/tests/environments/test_local.py +++ b/tests/environments/test_local.py @@ -19,6 +19,7 @@ def test_local_environment_config_defaults(): assert config.cwd == "" assert config.env == {} assert config.timeout == 30 + assert config.interpreter == ["bash", "-lc"] def test_local_environment_basic_execution(): @@ -262,3 +263,13 @@ def test_local_environment_shell_features(): result = env.execute({"command": "echo $(echo 'nested')"}) assert result["returncode"] == 0 assert "nested" in result["output"] + + +@pytest.mark.skipif(os.name == "nt", reason="requires bash") +def test_local_environment_uses_bash_by_default(): + """Test that the local environment supports bash-only syntax by default.""" + env = LocalEnvironment() + + result = env.execute({"command": "cat <(printf 'bash-only')"}) + assert result["returncode"] == 0 + assert result["output"] == "bash-only" From a89dbbdadf726c36c4cd7d9f2c9d8cadffc180ea Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 23:01:23 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/minisweagent/environments/local.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/minisweagent/environments/local.py b/src/minisweagent/environments/local.py index aff1eed92..64ea4893c 100644 --- a/src/minisweagent/environments/local.py +++ b/src/minisweagent/environments/local.py @@ -28,7 +28,9 @@ def execute(self, action: dict, cwd: str = "", *, timeout: int | None = None) -> command = action.get("command", "") cwd = cwd or self.config.cwd or os.getcwd() try: - result = _run(command, cwd, os.environ | self.config.env, timeout or self.config.timeout, self.config.interpreter) + result = _run( + command, cwd, os.environ | self.config.env, timeout or self.config.timeout, self.config.interpreter + ) output = {"output": result.stdout, "returncode": result.returncode, "exception_info": ""} except Exception as e: raw_output = getattr(e, "output", None)