Skip to content

Commit 4a00651

Browse files
grace227mdw771
authored andcommitted
FIX: preserve Python import paths in bubblewrap sandbox
1 parent 6e60afa commit 4a00651

2 files changed

Lines changed: 33 additions & 1 deletion

File tree

packages/eaa-core/src/eaa_core/tool/coding.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ def execute_code(
508508
elif self.sandbox_type == "bubblewrap":
509509
result = self._execute_in_bubblewrap(
510510
[os.path.realpath(sys.executable), "-c", prepared_code],
511-
env=env,
511+
env=self._build_bubblewrap_python_env(env),
512512
timeout=exec_timeout,
513513
input_text=input_text,
514514
workdir=exec_cwd,
@@ -573,6 +573,20 @@ def _default_python_image() -> str:
573573
"""Return the default Python container image name."""
574574
return f"python:{sys.version_info.major}.{sys.version_info.minor}"
575575

576+
def _build_bubblewrap_python_env(self, env: Dict[str, str]) -> Dict[str, str]:
577+
"""Preserve host import paths when running the resolved interpreter."""
578+
bubblewrap_env = env.copy()
579+
paths = self._bubblewrap_python_import_bind_paths()
580+
paths.extend((env.get("PYTHONPATH") or "").split(os.pathsep))
581+
resolved: list[str] = []
582+
for path in paths:
583+
if not path or path in resolved:
584+
continue
585+
resolved.append(path)
586+
if resolved:
587+
bubblewrap_env["PYTHONPATH"] = os.pathsep.join(resolved)
588+
return bubblewrap_env
589+
576590

577591
class BashCodingTool(CodingTool):
578592
"""Expose a tool that executes Bash code in an isolated subprocess."""

tests/test_coding_tool.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,27 @@ def test_python_bubblewrap_executes_resolved_interpreter(
198198
):
199199
venv_bin = tmp_path / ".venv" / "bin"
200200
runtime_bin = tmp_path / "runtime" / "bin"
201+
site_dir = tmp_path / ".venv" / "lib" / "python3.14" / "site-packages"
202+
source_dir = tmp_path / "src" / "pkg"
201203
venv_bin.mkdir(parents=True)
202204
runtime_bin.mkdir(parents=True)
205+
site_dir.mkdir(parents=True)
206+
source_dir.mkdir(parents=True)
203207
runtime_python = runtime_bin / "python3.14"
204208
runtime_python.write_text("")
209+
(site_dir / "__editable__.example.pth").write_text(f"{source_dir}\n")
205210
venv_python = venv_bin / "python"
206211
venv_python.symlink_to(runtime_python)
207212
monkeypatch.setattr("eaa_core.tool.coding.sys.executable", str(venv_python))
213+
monkeypatch.setattr(
214+
"eaa_core.tool.coding.site.getsitepackages",
215+
lambda: [str(site_dir)],
216+
)
217+
monkeypatch.setattr(
218+
"eaa_core.tool.coding.site.getusersitepackages",
219+
lambda: str(tmp_path / "missing-user-site"),
220+
)
221+
monkeypatch.setattr("eaa_core.tool.coding.sys.path", [str(site_dir)])
208222
captured = {}
209223

210224
def fake_which(name, path=None):
@@ -214,6 +228,7 @@ def fake_which(name, path=None):
214228

215229
def fake_run(command, **kwargs):
216230
captured["command"] = command
231+
captured["env"] = kwargs["env"]
217232
return subprocess.CompletedProcess(command, 0, stdout="ok\n", stderr="")
218233

219234
monkeypatch.setattr("eaa_core.tool.coding.shutil.which", fake_which)
@@ -228,6 +243,9 @@ def fake_run(command, **kwargs):
228243
chdir_index = captured["command"].index("--chdir")
229244
python_command = captured["command"][chdir_index + 2]
230245
assert python_command == str(runtime_python)
246+
pythonpath = captured["env"]["PYTHONPATH"].split(os.pathsep)
247+
assert str(site_dir) in pythonpath
248+
assert str(source_dir) in pythonpath
231249

232250

233251
if __name__ == "__main__":

0 commit comments

Comments
 (0)