fix(shell): preserve login PATH for path append
Made-with: Cursor
This commit is contained in:
@@ -135,7 +135,11 @@ class ExecTool(Tool):
|
|||||||
env = self._build_env()
|
env = self._build_env()
|
||||||
|
|
||||||
if self.path_append:
|
if self.path_append:
|
||||||
|
if _IS_WINDOWS:
|
||||||
env["PATH"] = env.get("PATH", "") + os.pathsep + self.path_append
|
env["PATH"] = env.get("PATH", "") + os.pathsep + self.path_append
|
||||||
|
else:
|
||||||
|
env["NANOBOT_PATH_APPEND"] = self.path_append
|
||||||
|
command = f'export PATH="$PATH{os.pathsep}$NANOBOT_PATH_APPEND"; {command}'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
process = await self._spawn(command, cwd, env)
|
process = await self._spawn(command, cwd, env)
|
||||||
@@ -255,7 +259,6 @@ class ExecTool(Tool):
|
|||||||
home = os.environ.get("HOME", "/tmp")
|
home = os.environ.get("HOME", "/tmp")
|
||||||
env = {
|
env = {
|
||||||
"HOME": home,
|
"HOME": home,
|
||||||
"PATH": os.environ.get ("PATH", "/usr/bin:/bin"),
|
|
||||||
"LANG": os.environ.get("LANG", "C.UTF-8"),
|
"LANG": os.environ.get("LANG", "C.UTF-8"),
|
||||||
"TERM": os.environ.get("TERM", "dumb"),
|
"TERM": os.environ.get("TERM", "dumb"),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class TestBuildEnvUnix:
|
|||||||
def test_expected_keys(self):
|
def test_expected_keys(self):
|
||||||
with patch("nanobot.agent.tools.shell._IS_WINDOWS", False):
|
with patch("nanobot.agent.tools.shell._IS_WINDOWS", False):
|
||||||
env = ExecTool()._build_env()
|
env = ExecTool()._build_env()
|
||||||
expected = {"HOME", "LANG", "PATH","TERM"}
|
expected = {"HOME", "LANG", "TERM"}
|
||||||
assert expected <= set(env)
|
assert expected <= set(env)
|
||||||
if sys.platform != "win32":
|
if sys.platform != "win32":
|
||||||
assert set(env) == expected
|
assert set(env) == expected
|
||||||
@@ -148,33 +148,33 @@ class TestSpawnWindows:
|
|||||||
class TestPathAppendPlatform:
|
class TestPathAppendPlatform:
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_unix_injects_export (self):
|
async def test_unix_uses_env_var_in_fixed_export(self):
|
||||||
"""On Unix, path_append is passed via the env dict, not shell string."""
|
"""On Unix, path_append must not be interpolated into shell source."""
|
||||||
mock_proc = AsyncMock ()
|
mock_proc = AsyncMock()
|
||||||
mock_proc.communicate.return_value = (b"ok", b"")
|
mock_proc.communicate.return_value = (b"ok", b"")
|
||||||
mock_proc.returncode = 0
|
mock_proc.returncode = 0
|
||||||
|
|
||||||
captured_cmd = None
|
captured_cmd = None
|
||||||
captured_env = {}
|
captured_env = {}
|
||||||
|
|
||||||
async def capture_spawn (cmd, cwd, env):
|
async def capture_spawn(cmd, cwd, env):
|
||||||
nonlocal captured_cmd
|
nonlocal captured_cmd
|
||||||
captured_cmd = cmd
|
captured_cmd = cmd
|
||||||
captured_env.update (env)
|
captured_env.update(env)
|
||||||
return mock_proc
|
return mock_proc
|
||||||
|
|
||||||
with (
|
with (
|
||||||
patch ("nanobot.agent.tools.shell._IS_WINDOWS", False),
|
patch("nanobot.agent.tools.shell._IS_WINDOWS", False),
|
||||||
patch ("nanobot.agent.tools.shell.os.pathsep", ":"),
|
patch("nanobot.agent.tools.shell.os.pathsep", ":"),
|
||||||
patch.object (ExecTool, "_spawn", side_effect=capture_spawn),
|
patch.object(ExecTool, "_spawn", side_effect=capture_spawn),
|
||||||
patch.object (ExecTool, "_guard_command", return_value=None),
|
patch.object(ExecTool, "_guard_command", return_value=None),
|
||||||
):
|
):
|
||||||
tool = ExecTool (path_append="/opt/bin")
|
tool = ExecTool(path_append="/opt/bin; echo INJECTED")
|
||||||
await tool.execute (command="ls")
|
await tool.execute(command="ls")
|
||||||
|
|
||||||
# path_append must be in the env dict, NOT injected into the command
|
assert captured_cmd == 'export PATH="$PATH:$NANOBOT_PATH_APPEND"; ls'
|
||||||
assert captured_cmd == "ls"
|
assert captured_env["NANOBOT_PATH_APPEND"] == "/opt/bin; echo INJECTED"
|
||||||
assert captured_env["PATH"].endswith (":/opt/bin")
|
assert "INJECTED" not in captured_cmd
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_windows_modifies_env(self):
|
async def test_windows_modifies_env(self):
|
||||||
@@ -191,7 +191,7 @@ class TestPathAppendPlatform:
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch("nanobot.agent.tools.shell._IS_WINDOWS", True),
|
patch("nanobot.agent.tools.shell._IS_WINDOWS", True),
|
||||||
patch ("nanobot.agent.tools.shell.os.pathsep", ";"),
|
patch("nanobot.agent.tools.shell.os.pathsep", ";"),
|
||||||
patch.object(ExecTool, "_spawn", side_effect=capture_spawn),
|
patch.object(ExecTool, "_spawn", side_effect=capture_spawn),
|
||||||
patch.object(ExecTool, "_guard_command", return_value=None),
|
patch.object(ExecTool, "_guard_command", return_value=None),
|
||||||
):
|
):
|
||||||
|
|||||||
Reference in New Issue
Block a user