from __future__ import annotations import asyncio import base64 import builtins import inspect import io import logging import shlex import tarfile import uuid from pathlib import Path from typing import Literal, cast import pytest from pydantic import Field, PrivateAttr import agents.extensions.sandbox.e2b.sandbox as e2b_module from agents.extensions.sandbox._rclone import ( ensure_rclone as _ensure_rclone, rclone_pattern_for_session as _rclone_pattern_for_session, ) from agents.extensions.sandbox.e2b.mounts import ( E2BCloudBucketMountStrategy, _assert_e2b_session, _ensure_fuse_support, ) from agents.extensions.sandbox.e2b.sandbox import ( E2BSandboxClient, E2BSandboxClientOptions, E2BSandboxSession, E2BSandboxSessionState, ) from agents.sandbox import Manifest from agents.sandbox.entries import ( Dir, InContainerMountStrategy, Mount, MountpointMountPattern, RcloneMountPattern, S3Mount, ) from agents.sandbox.entries.mounts.base import InContainerMountAdapter from agents.sandbox.errors import ( ExecTimeoutError, ExecTransportError, InvalidManifestPathError, MountConfigError, WorkspaceArchiveReadError, WorkspaceArchiveWriteError, WorkspaceStartError, ) from agents.sandbox.files import EntryKind from agents.sandbox.materialization import MaterializedFile from agents.sandbox.session.base_sandbox_session import BaseSandboxSession from agents.sandbox.session.dependencies import Dependencies from agents.sandbox.session.runtime_helpers import ( RESOLVE_WORKSPACE_PATH_HELPER, WORKSPACE_FINGERPRINT_HELPER, ) from agents.sandbox.snapshot import NoopSnapshot, SnapshotBase from agents.sandbox.types import ExecResult, User def test_e2b_package_re_exports_backend_symbols() -> None: package_module = __import__( "agents.extensions.sandbox.e2b", fromlist=["E2BCloudBucketMountStrategy", "E2BSandboxClient"], ) assert package_module.E2BCloudBucketMountStrategy is E2BCloudBucketMountStrategy assert package_module.E2BSandboxClient is E2BSandboxClient def test_e2b_extension_re_exports_cloud_bucket_strategy() -> None: package_module = __import__( "agents.extensions.sandbox", fromlist=["E2BCloudBucketMountStrategy"], ) assert package_module.E2BCloudBucketMountStrategy is E2BCloudBucketMountStrategy def test_e2b_mount_strategy_type_and_default_pattern() -> None: strategy = E2BCloudBucketMountStrategy() assert strategy.type == "e2b_cloud_bucket" assert isinstance(strategy.pattern, RcloneMountPattern) assert strategy.pattern.mode == "fuse" def test_e2b_mount_strategy_round_trips_through_manifest() -> None: manifest = Manifest.model_validate( { "root": "/workspace", "entries": { "bucket": { "type": "s3_mount", "bucket": "my-bucket", "mount_strategy": {"type": "e2b_cloud_bucket"}, } }, } ) mount = manifest.entries["bucket"] assert isinstance(mount, S3Mount) assert isinstance(mount.mount_strategy, E2BCloudBucketMountStrategy) def test_e2b_session_guard_rejects_wrong_type() -> None: class _WrongSession: pass with pytest.raises(MountConfigError, match="E2BSandboxSession"): _assert_e2b_session(_WrongSession()) # type: ignore[arg-type] def test_e2b_session_guard_accepts_correct_type() -> None: _assert_e2b_session(_FakeMountSession()) @pytest.mark.asyncio async def test_e2b_ensure_fuse_uses_root_chmod() -> None: session = _FakeMountSession([_exec_ok(), _exec_ok()]) await _ensure_fuse_support(session) assert session.exec_calls == [ ( "sh -lc test -c /dev/fuse && grep -qw fuse /proc/filesystems && " "(command -v fusermount3 >/dev/null 2>&1 || command -v fusermount >/dev/null 2>&1)" ), ( "sudo -u root -- sh -lc chmod a+rw /dev/fuse && " "touch /etc/fuse.conf && " "(grep -qxF user_allow_other /etc/fuse.conf || " "printf '\\nuser_allow_other\\n' >> /etc/fuse.conf)" ), ] @pytest.mark.asyncio async def test_e2b_ensure_rclone_installs_with_root_apt() -> None: session = _FakeMountSession( [ _exec_fail(), # rclone missing _exec_ok(), # apt-get present _exec_ok(), # apt-get update succeeds _exec_ok(), # package install succeeds _exec_ok(), # upstream rclone install succeeds _exec_ok(), # rclone now present ] ) await _ensure_rclone(session) assert session.exec_calls[:2] == [ "sh -lc command -v rclone >/dev/null 2>&1 || test -x /usr/local/bin/rclone", "sh -lc command -v apt-get >/dev/null 2>&1", ] assert session.exec_calls[2] == ( "sudo -u root -- sh -lc DEBIAN_FRONTEND=noninteractive " "DEBCONF_NOWARNINGS=yes apt-get -o Dpkg::Use-Pty=0 update -qq" ) assert session.exec_calls[3] == ( "sudo -u root -- sh -lc DEBIAN_FRONTEND=noninteractive " "DEBCONF_NOWARNINGS=yes apt-get -o Dpkg::Use-Pty=0 install -y -qq " "curl unzip ca-certificates" ) assert ( session.exec_calls[4] == "sudo -u root -- sh -lc curl -fsSL https://rclone.org/install.sh | bash" ) assert session.exec_calls[5] == ( "sh -lc command -v rclone >/dev/null 2>&1 || test -x /usr/local/bin/rclone" ) @pytest.mark.asyncio async def test_e2b_rclone_pattern_adds_fuse_access_args() -> None: session = _FakeMountSession([_exec_ok(stdout=b"1000\n1000\n")]) pattern = await _rclone_pattern_for_session(session, RcloneMountPattern(mode="fuse")) assert pattern.extra_args == ["--allow-other", "--uid", "1000", "--gid", "1000"] @pytest.mark.asyncio async def test_e2b_rclone_pattern_preserves_explicit_access_args() -> None: session = _FakeMountSession([_exec_ok(stdout=b"1000\n1000\n")]) source_pattern = RcloneMountPattern( mode="fuse", extra_args=["--allow-other", "--uid", "123", "--gid", "456", "--buffer-size", "0"], ) pattern = await _rclone_pattern_for_session(session, source_pattern) assert pattern.extra_args == [ "--allow-other", "--uid", "123", "--gid", "456", "--buffer-size", "0", ] class _FakeE2BResult: def __init__(self, *, stdout: str = "", stderr: str = "", exit_code: int = 0) -> None: self.stdout = stdout self.stderr = stderr self.exit_code = exit_code class _FakeE2BCommandExitException(Exception): def __init__(self, *, exit_code: int) -> None: super().__init__(f"command exited with {exit_code}") self.exit_code = exit_code class _FakeE2BAsyncCommandHandle: def __init__( self, *, result_exit_code: int = 0, initial_exit_code: int | None = None, wait_delay_s: float = 0, wait_error: BaseException | None = None, wait_never: bool = False, wait_until_released: bool = False, ) -> None: self.exit_code = initial_exit_code self.result_exit_code = result_exit_code self.wait_delay_s = wait_delay_s self.wait_error = wait_error self.wait_never = wait_never self.wait_until_released = wait_until_released self.wait_calls = 0 self.wait_cancelled = False self.kill_calls = 0 self._wait_released = asyncio.Event() async def wait(self) -> _FakeE2BResult: self.wait_calls += 1 try: if self.wait_never: await asyncio.Event().wait() if self.wait_until_released: await self._wait_released.wait() if self.wait_delay_s: await asyncio.sleep(self.wait_delay_s) if self.wait_error is not None: raise self.wait_error self.exit_code = self.result_exit_code return _FakeE2BResult(exit_code=self.result_exit_code) except asyncio.CancelledError: self.wait_cancelled = True raise async def kill(self) -> bool: self.kill_calls += 1 self.exit_code = 0 return True def release_wait(self) -> None: self._wait_released.set() class _FakeE2BFiles: def __init__(self) -> None: self.make_dir_calls: list[tuple[str, float | None]] = [] async def write( self, path: str, data: bytes, request_timeout: float | None = None, ) -> None: _ = (path, data, request_timeout) async def remove(self, path: str, request_timeout: float | None = None) -> None: _ = (path, request_timeout) async def make_dir(self, path: str, request_timeout: float | None = None) -> bool: self.make_dir_calls.append((path, request_timeout)) return True async def read(self, path: str, format: str = "bytes") -> bytes: _ = (path, format) return b"" class _FakeE2BCommands: def __init__(self) -> None: self.exec_root_ready = False self.calls: list[dict[str, object]] = [] self.mkdir_result: _FakeE2BResult | None = None self.next_result = _FakeE2BResult() self.background_calls: list[dict[str, object]] = [] self.background_error: BaseException | None = None self.next_async_command_handle: _FakeE2BAsyncCommandHandle | None = None self.async_command_stdout_chunks: list[bytes | str] = [] async def run( self, command: str, background: bool | None = None, envs: dict[str, str] | None = None, user: str | None = None, cwd: str | None = None, on_stdout: object | None = None, on_stderr: object | None = None, stdin: bool | None = None, timeout: float | None = None, request_timeout: float | None = None, ) -> object: _ = request_timeout if background: if self.background_error is not None: raise self.background_error _ = on_stderr self.background_calls.append( { "command": command, "timeout": timeout, "cwd": cwd, "envs": envs, "stdin": stdin, "background": background, } ) if callable(on_stdout): for chunk in self.async_command_stdout_chunks: result = on_stdout(chunk) if inspect.isawaitable(result): await result return self.next_async_command_handle or _FakeE2BAsyncCommandHandle() self.calls.append( { "command": command, "timeout": timeout, "cwd": cwd, "envs": envs, "user": user, } ) parts = shlex.split(command) if _is_helper_install_command(command): return _FakeE2BResult() if _is_helper_present_command(command): return _FakeE2BResult() if parts and parts[0] == str(RESOLVE_WORKSPACE_PATH_HELPER.install_path): return _FakeE2BResult(stdout=parts[2]) if parts and parts[0] == str(WORKSPACE_FINGERPRINT_HELPER.install_path): return _FakeE2BResult( stdout='{"fingerprint":"fake-workspace-fingerprint","version":"workspace_tar_sha256_v1"}\n' ) if command == "test -d /workspace" and cwd in (None, "/"): exit_code = 0 if self.exec_root_ready else 1 return _FakeE2BResult(exit_code=exit_code) if command == "mkdir -p -- /workspace" and cwd == "/": result = self.mkdir_result or _FakeE2BResult() if result.exit_code == 0: self.exec_root_ready = True self.mkdir_result = None return result if cwd == "/workspace" and not self.exec_root_ready: raise ValueError("cwd '/workspace' does not exist") result = self.next_result self.next_result = _FakeE2BResult() return result class _FakeE2BPtyHandle(_FakeE2BAsyncCommandHandle): def __init__( self, *, result_exit_code: int = 0, wait_delay_s: float = 0, wait_error: BaseException | None = None, wait_never: bool = True, ) -> None: super().__init__( result_exit_code=result_exit_code, wait_delay_s=wait_delay_s, wait_error=wait_error, wait_never=wait_never, ) self.pid = "pty-123" self.stdin_payloads: list[bytes] = [] class _FakeE2BPty: def __init__(self) -> None: self.handle = _FakeE2BPtyHandle() self.on_data: object | None = None self.stdin_output_chunks: list[bytes | str] = [] self.create_error: BaseException | None = None self.send_stdin_error: BaseException | None = None async def create( self, *, size: object, cwd: str | None = None, envs: dict[str, str] | None = None, timeout: float | None = None, on_data: object | None = None, ) -> _FakeE2BPtyHandle: _ = (size, cwd, envs, timeout) if self.create_error is not None: raise self.create_error self.on_data = on_data return self.handle async def send_stdin( self, pid: object, data: bytes, request_timeout: float | None = None, ) -> None: _ = (pid, request_timeout) if self.send_stdin_error is not None: raise self.send_stdin_error self.handle.stdin_payloads.append(data) if callable(self.on_data): for chunk in self.stdin_output_chunks: result = self.on_data(chunk) if inspect.isawaitable(result): await result self.stdin_output_chunks.clear() class _FakeE2BSandbox: def __init__(self) -> None: self.sandbox_id = "sb-123" self.files = _FakeE2BFiles() self.commands = _FakeE2BCommands() self.pty = _FakeE2BPty() self.created_snapshot_id = "snap-123" self.pause_error: BaseException | None = None self.kill_error: BaseException | None = None self.pause_calls = 0 self.kill_calls = 0 async def pause(self) -> None: self.pause_calls += 1 if self.pause_error is not None: raise self.pause_error return async def kill(self) -> None: self.kill_calls += 1 if self.kill_error is not None: raise self.kill_error return async def is_running(self, request_timeout: float | None = None) -> bool: _ = request_timeout return True def get_host(self, port: int) -> str: return f"{port}-{self.sandbox_id}.sandbox.example.test" async def create_snapshot(self) -> object: return type("SnapshotInfo", (), {"snapshot_id": self.created_snapshot_id})() class _FakeMountSession(BaseSandboxSession): __name__ = "E2BSandboxSession" def __init__(self, results: list[ExecResult] | None = None) -> None: self.state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id="sb-123", ) self._results = list(results or []) self.exec_calls: list[str] = [] async def _exec_internal( self, *command: str | Path, timeout: float | None = None, ) -> ExecResult: _ = timeout cmd_str = " ".join(str(c) for c in command) self.exec_calls.append(cmd_str) if self._results: return self._results.pop(0) return ExecResult(stdout=b"", stderr=b"", exit_code=0) async def read(self, path: Path, *, user: str | User | None = None) -> io.IOBase: _ = (path, user) return io.BytesIO(b"") async def write(self, path: Path, data: io.IOBase, *, user: str | User | None = None) -> None: _ = (path, data, user) async def persist_workspace(self) -> io.IOBase: raise AssertionError("not expected") async def hydrate_workspace(self, data: io.IOBase) -> None: _ = data raise AssertionError("not expected") async def running(self) -> bool: return True _FakeMountSession.__name__ = "E2BSandboxSession" def _exec_ok(stdout: bytes = b"") -> ExecResult: return ExecResult(stdout=stdout, stderr=b"", exit_code=0) def _exec_fail() -> ExecResult: return ExecResult(stdout=b"", stderr=b"", exit_code=1) class _RestorableSnapshot(SnapshotBase): type: Literal["test-restorable-e2b"] = "test-restorable-e2b" payload: bytes = b"restored" async def persist(self, data: io.IOBase, *, dependencies: Dependencies | None = None) -> None: _ = (data, dependencies) async def restore(self, *, dependencies: Dependencies | None = None) -> io.IOBase: _ = dependencies return io.BytesIO(self.payload) async def restorable(self, *, dependencies: Dependencies | None = None) -> bool: _ = dependencies return True class _RecordingMount(Mount): type: str = "recording_mount" mount_strategy: InContainerMountStrategy = Field( default_factory=lambda: InContainerMountStrategy(pattern=MountpointMountPattern()) ) _mounted_paths: list[Path] = PrivateAttr(default_factory=list) _unmounted_paths: list[Path] = PrivateAttr(default_factory=list) _events: list[tuple[str, str]] = PrivateAttr(default_factory=list) def bind_events(self, events: list[tuple[str, str]]) -> _RecordingMount: self._events = events return self def supported_in_container_patterns( self, ) -> tuple[builtins.type[MountpointMountPattern], ...]: return (MountpointMountPattern,) def build_docker_volume_driver_config( self, strategy: object, ) -> tuple[str, dict[str, str], bool]: _ = strategy raise MountConfigError( message="docker-volume mounts are not supported for this mount type", context={"mount_type": self.type}, ) def in_container_adapter(self) -> InContainerMountAdapter: mount = self class _Adapter(InContainerMountAdapter): def validate(self, strategy: InContainerMountStrategy) -> None: _ = strategy async def activate( self, strategy: InContainerMountStrategy, session: BaseSandboxSession, dest: Path, base_dir: Path, ) -> list[MaterializedFile]: _ = (strategy, session, base_dir) path = mount._resolve_mount_path(session, dest) mount._events.append(("mount", path.as_posix())) mount._mounted_paths.append(path) return [] async def deactivate( self, strategy: InContainerMountStrategy, session: BaseSandboxSession, dest: Path, base_dir: Path, ) -> None: _ = (strategy, session, base_dir) path = mount._resolve_mount_path(session, dest) mount._events.append(("unmount", path.as_posix())) mount._unmounted_paths.append(path) async def teardown_for_snapshot( self, strategy: InContainerMountStrategy, session: BaseSandboxSession, path: Path, ) -> None: _ = (strategy, session) mount._events.append(("unmount", path.as_posix())) mount._unmounted_paths.append(path) async def restore_after_snapshot( self, strategy: InContainerMountStrategy, session: BaseSandboxSession, path: Path, ) -> None: _ = (strategy, session) mount._events.append(("mount", path.as_posix())) mount._mounted_paths.append(path) return _Adapter(self) class _FailingUnmountMount(_RecordingMount): type: str = "failing_unmount_mount" def in_container_adapter(self) -> InContainerMountAdapter: mount = self base_adapter = super().in_container_adapter() class _Adapter(InContainerMountAdapter): def validate(self, strategy: InContainerMountStrategy) -> None: base_adapter.validate(strategy) async def activate( self, strategy: InContainerMountStrategy, session: BaseSandboxSession, dest: Path, base_dir: Path, ) -> list[MaterializedFile]: return await base_adapter.activate(strategy, session, dest, base_dir) async def deactivate( self, strategy: InContainerMountStrategy, session: BaseSandboxSession, dest: Path, base_dir: Path, ) -> None: _ = (strategy, session, base_dir) path = mount._resolve_mount_path(session, dest) mount._events.append(("unmount_fail", path.as_posix())) raise RuntimeError("boom while unmounting second mount") async def teardown_for_snapshot( self, strategy: InContainerMountStrategy, session: BaseSandboxSession, path: Path, ) -> None: _ = (strategy, session) mount._events.append(("unmount_fail", path.as_posix())) raise RuntimeError("boom while unmounting second mount") async def restore_after_snapshot( self, strategy: InContainerMountStrategy, session: BaseSandboxSession, path: Path, ) -> None: await base_adapter.restore_after_snapshot(strategy, session, path) return _Adapter(self) class _FailingRemountMount(_RecordingMount): type: str = "failing_remount_mount" def in_container_adapter(self) -> InContainerMountAdapter: mount = self base_adapter = super().in_container_adapter() class _Adapter(InContainerMountAdapter): def validate(self, strategy: InContainerMountStrategy) -> None: base_adapter.validate(strategy) async def activate( self, strategy: InContainerMountStrategy, session: BaseSandboxSession, dest: Path, base_dir: Path, ) -> list[MaterializedFile]: _ = (strategy, session, base_dir) path = mount._resolve_mount_path(session, dest) mount._events.append(("mount_fail", path.as_posix())) raise RuntimeError("boom while remounting second mount") async def deactivate( self, strategy: InContainerMountStrategy, session: BaseSandboxSession, dest: Path, base_dir: Path, ) -> None: return await base_adapter.deactivate(strategy, session, dest, base_dir) async def teardown_for_snapshot( self, strategy: InContainerMountStrategy, session: BaseSandboxSession, path: Path, ) -> None: await base_adapter.teardown_for_snapshot(strategy, session, path) async def restore_after_snapshot( self, strategy: InContainerMountStrategy, session: BaseSandboxSession, path: Path, ) -> None: _ = (strategy, session) mount._events.append(("mount_fail", path.as_posix())) raise RuntimeError("boom while remounting second mount") return _Adapter(self) def _session( *, workspace_root_ready: bool = False, exposed_ports: tuple[int, ...] = (), ) -> tuple[E2BSandboxSession, _FakeE2BSandbox]: sandbox = _FakeE2BSandbox() state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=workspace_root_ready, exposed_ports=exposed_ports, ) return E2BSandboxSession.from_state(state, sandbox=sandbox), sandbox def _tar_bytes() -> bytes: buf = io.BytesIO() with tarfile.open(fileobj=buf, mode="w") as tar: info = tarfile.TarInfo("note.txt") payload = b"hello" info.size = len(payload) tar.addfile(info, io.BytesIO(payload)) return buf.getvalue() @pytest.mark.asyncio async def test_e2b_sandbox_connect_prefers_full_sandbox_wrapper() -> None: class _FakeSandboxClass: calls: list[tuple[str, str, int | None]] = [] @classmethod async def connect(cls, *, sandbox_id: str, timeout: int | None = None) -> str: cls.calls.append(("connect", sandbox_id, timeout)) return "full-sandbox-wrapper" @classmethod async def _cls_connect_sandbox(cls, *, sandbox_id: str, timeout: int | None = None) -> str: cls.calls.append(("_cls_connect_sandbox", sandbox_id, timeout)) return "private-full-sandbox-wrapper" @classmethod async def _cls_connect(cls, *, sandbox_id: str, timeout: int | None = None) -> str: cls.calls.append(("_cls_connect", sandbox_id, timeout)) return "low-level-api-model" connected = await e2b_module._sandbox_connect( cast(e2b_module._E2BSandboxFactoryAPI, _FakeSandboxClass), sandbox_id="sb-123", timeout=300, ) assert connected == "full-sandbox-wrapper" assert _FakeSandboxClass.calls == [("connect", "sb-123", 300)] def test_e2b_import_resolves_sdk_sandbox_classes_for_canonical_types( monkeypatch: pytest.MonkeyPatch, ) -> None: imports: list[str] = [] real_import = builtins.__import__ def _fake_import( name: str, globals: dict[str, object] | None = None, locals: dict[str, object] | None = None, fromlist: tuple[str, ...] = (), level: int = 0, ) -> object: if name == "e2b_code_interpreter": imports.append(name) return type("FakeCodeInterpreterModule", (), {"AsyncSandbox": object()})() if name == "e2b": imports.append(name) return type("FakeE2BModule", (), {"AsyncSandbox": object()})() return real_import(name, globals, locals, fromlist, level) monkeypatch.setattr(builtins, "__import__", _fake_import) assert e2b_module._import_sandbox_class(e2b_module.E2BSandboxType.CODE_INTERPRETER) is not None assert e2b_module._import_sandbox_class(e2b_module.E2BSandboxType.E2B) is not None assert imports == ["e2b_code_interpreter", "e2b"] def _visible_command_calls(sandbox: _FakeE2BSandbox) -> list[dict[str, object]]: return [ call for call in sandbox.commands.calls if not _is_helper_install_command(str(call["command"])) and not _is_helper_present_command(str(call["command"])) and not _is_helper_invoke_command(str(call["command"])) ] def _is_helper_install_command(command: str) -> bool: return RESOLVE_WORKSPACE_PATH_HELPER.install_marker in command def _is_helper_invoke_command(command: str) -> bool: parts = shlex.split(command) return bool(parts) and parts[0].startswith("/tmp/openai-agents/bin/") def _is_helper_present_command(command: str) -> bool: parts = shlex.split(command) return ( len(parts) == 3 and parts[:2] == ["test", "-x"] and parts[2].startswith("/tmp/openai-agents/bin/") ) @pytest.mark.asyncio async def test_e2b_exec_omits_cwd_until_workspace_ready() -> None: session, sandbox = _session(workspace_root_ready=False) result = await session._exec_internal("find", ".", timeout=0.01) # noqa: SLF001 assert result.ok() assert sandbox.commands.calls == [ { "command": "find .", "timeout": 0.01, "cwd": None, "envs": {}, "user": None, } ] @pytest.mark.asyncio async def test_e2b_exec_uses_manifest_root_after_workspace_ready() -> None: session, sandbox = _session(workspace_root_ready=True) sandbox.commands.exec_root_ready = True result = await session._exec_internal("find", ".", timeout=0.01) # noqa: SLF001 assert result.ok() assert sandbox.commands.calls == [ { "command": "find .", "timeout": 0.01, "cwd": "/workspace", "envs": {}, "user": None, } ] @pytest.mark.asyncio async def test_e2b_start_prepares_workspace_root_for_command_cwd() -> None: session, sandbox = _session(workspace_root_ready=False) await session.start() result = await session._exec_internal("pwd", timeout=0.01) # noqa: SLF001 assert result.ok() assert session.state.workspace_root_ready is True assert session._workspace_root_ready is True # noqa: SLF001 assert _visible_command_calls(sandbox) == [ { "command": "mkdir -p -- /workspace", "timeout": 10, "cwd": "/", "envs": {}, "user": None, }, { "command": "pwd", "timeout": 0.01, "cwd": "/workspace", "envs": {}, "user": None, }, ] @pytest.mark.asyncio async def test_e2b_start_installs_runtime_helpers() -> None: session, sandbox = _session(workspace_root_ready=False) await session.start() assert any(_is_helper_install_command(str(call["command"])) for call in sandbox.commands.calls) @pytest.mark.asyncio async def test_e2b_start_raises_on_nonzero_workspace_root_setup_exit() -> None: session, sandbox = _session(workspace_root_ready=False) sandbox.commands.mkdir_result = _FakeE2BResult(stderr="mkdir failed", exit_code=2) with pytest.raises(WorkspaceStartError) as exc_info: await session.start() assert exc_info.value.context["reason"] == "workspace_root_nonzero_exit" assert exc_info.value.context["exit_code"] == 2 assert session.state.workspace_root_ready is False assert session._workspace_root_ready is False # noqa: SLF001 @pytest.mark.asyncio async def test_e2b_preserved_start_still_prepares_workspace_root_for_resumed_exec_cwd() -> None: session, sandbox = _session(workspace_root_ready=False) session._set_start_state_preserved(True) # noqa: SLF001 await session.start() result = await session._exec_internal("pwd", timeout=0.01) # noqa: SLF001 assert result.ok() assert session.state.workspace_root_ready is True assert session._workspace_root_ready is True # noqa: SLF001 assert session._can_reuse_preserved_workspace_on_resume() is False # noqa: SLF001 assert session.should_provision_manifest_accounts_on_resume() is False assert _visible_command_calls(sandbox) == [ { "command": "test -d /workspace", "timeout": 10.0, "cwd": None, "envs": {}, "user": None, }, { "command": "mkdir -p -- /workspace", "timeout": 10, "cwd": "/", "envs": {}, "user": None, }, { "command": "pwd", "timeout": 0.01, "cwd": "/workspace", "envs": {}, "user": None, }, ] @pytest.mark.asyncio async def test_e2b_preserved_start_uses_shared_resume_gate_for_restore() -> None: session, _sandbox = _session(workspace_root_ready=True) session.state.snapshot = _RestorableSnapshot(id="snapshot") session._set_start_state_preserved(True) # noqa: SLF001 events: list[object] = [] async def _gate(*, is_running: bool) -> bool: events.append(("gate", is_running)) return False async def _restore() -> None: events.append("restore") async def _reapply() -> None: events.append("reapply") session._can_skip_snapshot_restore_on_resume = _gate # type: ignore[method-assign] session._restore_snapshot_into_workspace_on_resume = _restore # type: ignore[method-assign] session._reapply_ephemeral_manifest_on_resume = _reapply # type: ignore[method-assign] await session.start() assert session.state.workspace_root_ready is True assert session._workspace_root_ready is True # noqa: SLF001 assert events == [("gate", True), "restore", "reapply"] @pytest.mark.asyncio async def test_e2b_running_requires_workspace_root_ready() -> None: session, _sandbox = _session(workspace_root_ready=False) assert await session.running() is False @pytest.mark.asyncio async def test_e2b_running_checks_remote_after_workspace_ready() -> None: session, sandbox = _session(workspace_root_ready=True) sandbox.commands.exec_root_ready = True assert await session.running() is True @pytest.mark.asyncio async def test_e2b_resolve_exposed_port_uses_backend_host() -> None: session, _sandbox = _session(workspace_root_ready=True, exposed_ports=(8765,)) endpoint = await session.resolve_exposed_port(8765) assert endpoint.host == "8765-sb-123.sandbox.example.test" assert endpoint.port == 443 assert endpoint.tls is True @pytest.mark.asyncio async def test_e2b_client_create_enables_public_traffic_for_exposed_ports( monkeypatch: pytest.MonkeyPatch, ) -> None: create_calls: list[dict[str, object]] = [] class _FakeSandboxFactory: @staticmethod async def create( *, template: str | None = None, timeout: int | None = None, metadata: dict[str, str] | None = None, envs: dict[str, str] | None = None, secure: bool = True, allow_internet_access: bool = True, network: dict[str, object] | None = None, lifecycle: dict[str, object] | None = None, mcp: dict[str, dict[str, str]] | None = None, ) -> _FakeE2BSandbox: _ = ( template, timeout, metadata, envs, secure, allow_internet_access, network, lifecycle, mcp, ) create_calls.append( { "template": template, "timeout": timeout, "metadata": metadata, "envs": envs, "secure": secure, "allow_internet_access": allow_internet_access, "network": network, "lifecycle": lifecycle, "mcp": mcp, } ) return _FakeE2BSandbox() monkeypatch.setattr( e2b_module, "_import_sandbox_class", lambda _sandbox_type: _FakeSandboxFactory ) client = E2BSandboxClient() session = await client.create( options=E2BSandboxClientOptions( sandbox_type="e2b", exposed_ports=(8765,), ) ) assert create_calls assert create_calls[0]["network"] == {"allow_public_traffic": True} assert create_calls[0]["lifecycle"] == {"on_timeout": "pause", "auto_resume": True} assert isinstance(session.state, E2BSandboxSessionState) assert session.state.exposed_ports == (8765,) assert session.state.on_timeout == "pause" assert session.state.auto_resume is True @pytest.mark.asyncio async def test_e2b_client_create_omits_auto_resume_for_kill_timeout( monkeypatch: pytest.MonkeyPatch, ) -> None: create_calls: list[dict[str, object]] = [] class _FakeSandboxFactory: @staticmethod async def create( *, template: str | None = None, timeout: int | None = None, metadata: dict[str, str] | None = None, envs: dict[str, str] | None = None, secure: bool = True, allow_internet_access: bool = True, network: dict[str, object] | None = None, lifecycle: dict[str, object] | None = None, mcp: dict[str, dict[str, str]] | None = None, ) -> _FakeE2BSandbox: _ = ( template, timeout, metadata, envs, secure, allow_internet_access, network, lifecycle, mcp, ) create_calls.append({"lifecycle": lifecycle}) return _FakeE2BSandbox() monkeypatch.setattr( e2b_module, "_import_sandbox_class", lambda _sandbox_type: _FakeSandboxFactory ) client = E2BSandboxClient() session = await client.create( options=E2BSandboxClientOptions( sandbox_type="e2b", on_timeout="kill", ) ) assert create_calls == [{"lifecycle": {"on_timeout": "kill"}}] assert isinstance(session.state, E2BSandboxSessionState) assert session.state.on_timeout == "kill" assert session.state.auto_resume is True @pytest.mark.asyncio async def test_e2b_client_create_passes_mcp_config( monkeypatch: pytest.MonkeyPatch, ) -> None: create_calls: list[dict[str, object]] = [] class _FakeSandboxFactory: @staticmethod async def create( *, template: str | None = None, timeout: int | None = None, metadata: dict[str, str] | None = None, envs: dict[str, str] | None = None, secure: bool = True, allow_internet_access: bool = True, network: dict[str, object] | None = None, lifecycle: dict[str, object] | None = None, mcp: dict[str, dict[str, str]] | None = None, ) -> _FakeE2BSandbox: _ = ( template, timeout, metadata, envs, secure, allow_internet_access, network, lifecycle, mcp, ) create_calls.append({"mcp": mcp}) return _FakeE2BSandbox() monkeypatch.setattr( e2b_module, "_import_sandbox_class", lambda _sandbox_type: _FakeSandboxFactory ) client = E2BSandboxClient() await client.create( options=E2BSandboxClientOptions( sandbox_type="e2b", mcp={ "exa": {"apiKey": "exa-key"}, "browserbase": { "apiKey": "browserbase-key", "geminiApiKey": "gemini-key", "projectId": "project-id", }, }, ) ) assert create_calls == [ { "mcp": { "exa": {"apiKey": "exa-key"}, "browserbase": { "apiKey": "browserbase-key", "geminiApiKey": "gemini-key", "projectId": "project-id", }, } } ] def test_e2b_deserialize_session_state_defaults_missing_mcp() -> None: state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id="sb-123", mcp={"exa": {"apiKey": "exa-key"}}, ) payload = state.model_dump(mode="python") payload.pop("mcp") restored = E2BSandboxClient().deserialize_session_state(cast(dict[str, object], payload)) assert isinstance(restored, E2BSandboxSessionState) assert restored.mcp is None def test_e2b_client_options_preserves_positional_exposed_ports() -> None: options = E2BSandboxClientOptions( "e2b", None, None, None, None, True, True, None, False, (8765,), ) assert options.exposed_ports == (8765,) assert options.workspace_persistence == "tar" assert options.on_timeout == "pause" assert options.auto_resume is True @pytest.mark.asyncio async def test_e2b_resume_reuses_paused_timeout_lifecycle_sandbox( monkeypatch: pytest.MonkeyPatch, ) -> None: created: list[dict[str, object]] = [] connected: list[tuple[str, int | None]] = [] class _FakeSandboxFactory: @staticmethod async def create(**kwargs: object) -> _FakeE2BSandbox: created.append(dict(kwargs)) return _FakeE2BSandbox() @staticmethod async def connect(*, sandbox_id: str, timeout: int | None = None) -> _FakeE2BSandbox: connected.append((sandbox_id, timeout)) sandbox = _FakeE2BSandbox() sandbox.sandbox_id = sandbox_id return sandbox monkeypatch.setattr( e2b_module, "_import_sandbox_class", lambda _sandbox_type: _FakeSandboxFactory ) client = E2BSandboxClient() state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id="sb-paused", sandbox_timeout=15, on_timeout="pause", auto_resume=True, pause_on_exit=False, ) resumed = await client.resume(state) assert connected == [("sb-paused", 15)] assert created == [] assert isinstance(resumed.state, E2BSandboxSessionState) assert resumed.state.sandbox_id == "sb-paused" assert isinstance(resumed._inner, E2BSandboxSession) assert resumed._inner._workspace_state_preserved_on_start() is True # noqa: SLF001 assert resumed._inner._system_state_preserved_on_start() is True # noqa: SLF001 @pytest.mark.asyncio async def test_e2b_resume_reuses_live_kill_timeout_sandbox( monkeypatch: pytest.MonkeyPatch, ) -> None: created: list[dict[str, object]] = [] connected: list[tuple[str, int | None]] = [] class _LiveSandbox(_FakeE2BSandbox): async def is_running(self, request_timeout: float | None = None) -> bool: _ = request_timeout return True class _FakeSandboxFactory: @staticmethod async def create(**kwargs: object) -> _FakeE2BSandbox: created.append(dict(kwargs)) return _FakeE2BSandbox() @staticmethod async def connect(*, sandbox_id: str, timeout: int | None = None) -> _LiveSandbox: connected.append((sandbox_id, timeout)) sandbox = _LiveSandbox() sandbox.sandbox_id = sandbox_id return sandbox monkeypatch.setattr( e2b_module, "_import_sandbox_class", lambda _sandbox_type: _FakeSandboxFactory ) client = E2BSandboxClient() state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id="sb-live", sandbox_timeout=15, workspace_root_ready=True, on_timeout="kill", auto_resume=True, pause_on_exit=False, ) resumed = await client.resume(state) assert connected == [("sb-live", 15)] assert created == [] assert isinstance(resumed.state, E2BSandboxSessionState) assert resumed.state.sandbox_id == "sb-live" assert resumed._inner._workspace_state_preserved_on_start() is True # noqa: SLF001 assert resumed._inner._system_state_preserved_on_start() is True # noqa: SLF001 @pytest.mark.asyncio async def test_e2b_resume_recreates_dead_kill_timeout_sandbox_and_preserves_mcp( monkeypatch: pytest.MonkeyPatch, ) -> None: created: list[dict[str, object]] = [] connected: list[tuple[str, int | None]] = [] class _DeadSandbox(_FakeE2BSandbox): async def is_running(self, request_timeout: float | None = None) -> bool: _ = request_timeout return False class _CreatedSandbox(_FakeE2BSandbox): def __init__(self) -> None: super().__init__() self.sandbox_id = "sb-recreated" class _FakeSandboxFactory: @staticmethod async def create( *, template: str | None = None, timeout: int | None = None, metadata: dict[str, str] | None = None, envs: dict[str, str] | None = None, secure: bool = True, allow_internet_access: bool = True, network: dict[str, object] | None = None, lifecycle: dict[str, object] | None = None, mcp: dict[str, dict[str, str]] | None = None, ) -> _CreatedSandbox: _ = ( template, timeout, metadata, envs, secure, allow_internet_access, network, lifecycle, mcp, ) created.append( { "template": template, "timeout": timeout, "metadata": metadata, "envs": envs, "secure": secure, "allow_internet_access": allow_internet_access, "network": network, "lifecycle": lifecycle, "mcp": mcp, } ) return _CreatedSandbox() @staticmethod async def connect(*, sandbox_id: str, timeout: int | None = None) -> _DeadSandbox: connected.append((sandbox_id, timeout)) sandbox = _DeadSandbox() sandbox.sandbox_id = sandbox_id return sandbox monkeypatch.setattr( e2b_module, "_import_sandbox_class", lambda _sandbox_type: _FakeSandboxFactory ) client = E2BSandboxClient() state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id="sb-dead", sandbox_timeout=15, workspace_root_ready=True, on_timeout="kill", auto_resume=True, pause_on_exit=False, mcp={"exa": {"apiKey": "exa-key"}}, ) resumed = await client.resume(state) assert connected == [("sb-dead", 15)] assert created == [ { "template": None, "timeout": 15, "metadata": None, "envs": None, "secure": True, "allow_internet_access": True, "network": None, "lifecycle": {"on_timeout": "kill"}, "mcp": {"exa": {"apiKey": "exa-key"}}, } ] assert isinstance(resumed.state, E2BSandboxSessionState) assert resumed.state.sandbox_id == "sb-recreated" assert resumed.state.workspace_root_ready is False assert resumed._inner._workspace_state_preserved_on_start() is False # noqa: SLF001 assert resumed._inner._system_state_preserved_on_start() is False # noqa: SLF001 @pytest.mark.asyncio async def test_e2b_normalize_path_preserves_safe_leaf_symlink_path( monkeypatch: pytest.MonkeyPatch, ) -> None: session, _sandbox = _session(workspace_root_ready=True) async def _fake_exec( *command: object, timeout: float | None = None, shell: bool | list[str] = True, user: object | None = None, ) -> ExecResult: _ = (timeout, shell, user) rendered = [str(part) for part in command] if ( rendered[:2] == ["sh", "-c"] and RESOLVE_WORKSPACE_PATH_HELPER.install_marker in rendered[2] ): return ExecResult(stdout=b"", stderr=b"", exit_code=0) if rendered and rendered[0] == str(RESOLVE_WORKSPACE_PATH_HELPER.install_path): return ExecResult(stdout=b"/workspace/target.txt", stderr=b"", exit_code=0) raise AssertionError(f"unexpected command: {rendered!r}") monkeypatch.setattr(session, "exec", _fake_exec) normalized = await session._validate_path_access("link.txt") # noqa: SLF001 assert normalized == Path("/workspace/link.txt") @pytest.mark.asyncio async def test_e2b_normalize_path_rejects_symlink_escape( monkeypatch: pytest.MonkeyPatch, ) -> None: session, _sandbox = _session(workspace_root_ready=True) async def _fake_exec( *command: object, timeout: float | None = None, shell: bool | list[str] = True, user: object | None = None, ) -> ExecResult: _ = (timeout, shell, user) rendered = [str(part) for part in command] if ( rendered[:2] == ["sh", "-c"] and RESOLVE_WORKSPACE_PATH_HELPER.install_marker in rendered[2] ): return ExecResult(stdout=b"", stderr=b"", exit_code=0) if rendered and rendered[0] == str(RESOLVE_WORKSPACE_PATH_HELPER.install_path): return ExecResult(stdout=b"", stderr=b"workspace escape", exit_code=111) raise AssertionError(f"unexpected command: {rendered!r}") monkeypatch.setattr(session, "exec", _fake_exec) with pytest.raises(InvalidManifestPathError, match="must not escape root"): await session._validate_path_access("link/secret.txt") # noqa: SLF001 @pytest.mark.asyncio async def test_e2b_persist_workspace_raises_on_nonzero_snapshot_exit() -> None: session, sandbox = _session(workspace_root_ready=True) sandbox.commands.exec_root_ready = True sandbox.commands.next_result = _FakeE2BResult(stderr="tar failed", exit_code=2) with pytest.raises(WorkspaceArchiveReadError) as exc_info: await session.persist_workspace() assert exc_info.value.context["reason"] == "snapshot_nonzero_exit" assert exc_info.value.context["exit_code"] == 2 assert exc_info.value.retryable is False @pytest.mark.asyncio async def test_e2b_persist_workspace_excludes_runtime_skip_paths() -> None: session, sandbox = _session(workspace_root_ready=True) sandbox.commands.exec_root_ready = True session.register_persist_workspace_skip_path(Path("logs/events.jsonl")) sandbox.commands.next_result = _FakeE2BResult( stdout=base64.b64encode(b"fake-tar-bytes").decode("ascii") ) archive = await session.persist_workspace() assert archive.read() == b"fake-tar-bytes" expected_command = ( "tar --exclude=logs/events.jsonl --exclude=./logs/events.jsonl " "-C /workspace -cf - . | base64 -w0" ) assert sandbox.commands.calls == [ { "command": expected_command, "timeout": session.state.timeouts.snapshot_tar_s, "cwd": "/", "envs": {}, "user": None, } ] @pytest.mark.asyncio async def test_e2b_persist_workspace_native_snapshot_returns_snapshot_ref() -> None: session, sandbox = _session(workspace_root_ready=True) session.state.workspace_persistence = "snapshot" archive = await session.persist_workspace() assert archive.read() == e2b_module._encode_e2b_snapshot_ref(snapshot_id="snap-123") assert sandbox.commands.calls == [] @pytest.mark.asyncio async def test_e2b_persist_workspace_native_snapshot_times_out_and_remounts_mounts() -> None: events: list[tuple[str, str]] = [] mount = _RecordingMount().bind_events(events) class _SlowSnapshotSandbox(_FakeE2BSandbox): async def create_snapshot(self) -> object: await asyncio.sleep(0.2) return await super().create_snapshot() sandbox = _SlowSnapshotSandbox() state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace", entries={"mount": mount}), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, workspace_persistence="snapshot", ) state.timeouts.snapshot_tar_s = 0.01 session = E2BSandboxSession.from_state(state, sandbox=sandbox) with pytest.raises(WorkspaceArchiveReadError) as exc_info: await session.persist_workspace() assert exc_info.value.context["reason"] == "native_snapshot_failed" assert type(exc_info.value.cause).__name__ == "TimeoutError" assert events == [ ("unmount", "/workspace/mount"), ("mount", "/workspace/mount"), ] @pytest.mark.asyncio async def test_e2b_persist_workspace_native_snapshot_falls_back_to_tar_for_plain_skip_paths() -> ( None ): session, sandbox = _session(workspace_root_ready=True) session.state.workspace_persistence = "snapshot" session.register_persist_workspace_skip_path(Path("logs/events.jsonl")) sandbox.commands.exec_root_ready = True sandbox.commands.next_result = _FakeE2BResult( stdout=base64.b64encode(b"fake-tar-bytes").decode("ascii") ) archive = await session.persist_workspace() assert archive.read() == b"fake-tar-bytes" assert sandbox.commands.calls @pytest.mark.asyncio async def test_e2b_hydrate_workspace_native_snapshot_recreates_from_snapshot_id( monkeypatch: pytest.MonkeyPatch, ) -> None: session, sandbox = _session(workspace_root_ready=True) session.state.workspace_persistence = "snapshot" session.state.mcp = {"exa": {"apiKey": "exa-key"}} created: list[dict[str, object]] = [] class _CreatedSandbox(_FakeE2BSandbox): def __init__(self) -> None: super().__init__() self.sandbox_id = "sb-from-snapshot" class _FakeSandboxFactory: @staticmethod async def create(**kwargs: object) -> _CreatedSandbox: created.append(dict(kwargs)) return _CreatedSandbox() monkeypatch.setattr( e2b_module, "_import_sandbox_class", lambda _sandbox_type: _FakeSandboxFactory ) payload = io.BytesIO(e2b_module._encode_e2b_snapshot_ref(snapshot_id="snap-123")) await session.hydrate_workspace(payload) assert created == [ { "template": "snap-123", "timeout": session.state.sandbox_timeout, "metadata": session.state.metadata, "envs": None, "secure": session.state.secure, "allow_internet_access": session.state.allow_internet_access, "network": None, "lifecycle": {"on_timeout": "pause", "auto_resume": True}, "mcp": {"exa": {"apiKey": "exa-key"}}, } ] assert session.state.sandbox_id == "sb-from-snapshot" assert session.state.workspace_root_ready is True @pytest.mark.asyncio async def test_e2b_hydrate_workspace_raises_on_nonzero_extract_exit() -> None: session, sandbox = _session(workspace_root_ready=False) sandbox.commands.next_result = _FakeE2BResult(stderr="tar failed", exit_code=2) with pytest.raises(WorkspaceArchiveWriteError) as exc_info: await session.hydrate_workspace(io.BytesIO(_tar_bytes())) assert exc_info.value.context["reason"] == "hydrate_nonzero_exit" assert exc_info.value.context["exit_code"] == 2 assert session.state.workspace_root_ready is False assert session._workspace_root_ready is False # noqa: SLF001 @pytest.mark.asyncio async def test_e2b_persist_workspace_remounts_mounts_after_snapshot() -> None: mount = _RecordingMount() sandbox = _FakeE2BSandbox() sandbox.commands.exec_root_ready = True sandbox.commands.next_result = _FakeE2BResult( stdout=base64.b64encode(b"fake-tar-bytes").decode("ascii") ) state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace", entries={"mount": mount}), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) archive = await session.persist_workspace() assert archive.read() == b"fake-tar-bytes" assert mount._unmounted_paths == [Path("/workspace/mount")] assert mount._mounted_paths == [Path("/workspace/mount")] @pytest.mark.asyncio async def test_e2b_persist_workspace_uses_nested_mount_targets_and_resolved_excludes() -> None: parent_mount = _RecordingMount(mount_path=Path("repo")) child_mount = _RecordingMount(mount_path=Path("repo/sub")) events: list[tuple[str, str]] = [] sandbox = _FakeE2BSandbox() sandbox.commands.exec_root_ready = True sandbox.commands.next_result = _FakeE2BResult( stdout=base64.b64encode(b"fake-tar-bytes").decode("ascii") ) state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest( root="/workspace", entries={ "parent": parent_mount.bind_events(events), "nested": Dir(children={"child": child_mount.bind_events(events)}), }, ), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) archive = await session.persist_workspace() assert archive.read() == b"fake-tar-bytes" assert [path for kind, path in events if kind == "unmount"] == [ "/workspace/repo/sub", "/workspace/repo", ] assert [path for kind, path in events if kind == "mount"] == [ "/workspace/repo", "/workspace/repo/sub", ] tar_command = str(sandbox.commands.calls[-1]["command"]) assert "--exclude=repo" in tar_command assert "--exclude=./repo" in tar_command assert "--exclude=repo/sub" in tar_command assert "--exclude=./repo/sub" in tar_command @pytest.mark.asyncio async def test_e2b_persist_workspace_remounts_prior_mounts_after_unmount_failure() -> None: events: list[tuple[str, str]] = [] sandbox = _FakeE2BSandbox() sandbox.commands.exec_root_ready = True state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest( root="/workspace", entries={ "repo": Dir( children={ "mount1": _RecordingMount().bind_events(events), "mount2": _FailingUnmountMount().bind_events(events), } ) }, ), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) with pytest.raises(WorkspaceArchiveReadError): await session.persist_workspace() assert [kind for kind, _path in events] == [ "unmount", "unmount_fail", "mount", ] assert sandbox.commands.calls == [] @pytest.mark.asyncio async def test_e2b_persist_workspace_keeps_remounting_and_raises_remount_error_first() -> None: events: list[tuple[str, str]] = [] sandbox = _FakeE2BSandbox() sandbox.commands.exec_root_ready = True sandbox.commands.next_result = _FakeE2BResult(stderr="tar failed", exit_code=2) state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest( root="/workspace", entries={ "repo": Dir( children={ "a": _RecordingMount().bind_events(events), "b": _FailingRemountMount().bind_events(events), } ) }, ), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) with pytest.raises(WorkspaceArchiveReadError) as exc_info: await session.persist_workspace() assert isinstance(exc_info.value.cause, RuntimeError) assert str(exc_info.value.cause) == "boom while remounting second mount" assert exc_info.value.context["snapshot_error_before_remount_corruption"] == { "message": "failed to read archive for path: /workspace", } assert [kind for kind, _path in events] == [ "unmount", "unmount", "mount_fail", "mount", ] @pytest.mark.asyncio async def test_e2b_clear_workspace_root_on_resume_preserves_nested_mounts( monkeypatch: pytest.MonkeyPatch, ) -> None: session, _sandbox = _session() session.state.manifest = Manifest( root="/workspace", entries={ "a/b": _RecordingMount(), }, ) ls_calls: list[Path] = [] rm_calls: list[tuple[Path, bool]] = [] async def _fake_ls(path: Path | str) -> list[object]: rendered = Path(path) ls_calls.append(rendered) if rendered == Path("/workspace"): return [ type("Entry", (), {"path": "/workspace/a", "kind": EntryKind.DIRECTORY})(), type("Entry", (), {"path": "/workspace/root.txt", "kind": EntryKind.FILE})(), ] if rendered == Path("/workspace/a"): return [ type("Entry", (), {"path": "/workspace/a/b", "kind": EntryKind.DIRECTORY})(), type("Entry", (), {"path": "/workspace/a/local.txt", "kind": EntryKind.FILE})(), ] raise AssertionError(f"unexpected ls path: {rendered}") async def _fake_rm(path: Path | str, *, recursive: bool = False) -> None: rm_calls.append((Path(path), recursive)) monkeypatch.setattr(session, "ls", _fake_ls) monkeypatch.setattr(session, "rm", _fake_rm) await session._clear_workspace_root_on_resume() # noqa: SLF001 assert ls_calls == [Path("/workspace"), Path("/workspace/a")] assert rm_calls == [ (Path("/workspace/a/local.txt"), True), (Path("/workspace/root.txt"), True), ] @pytest.mark.asyncio async def test_e2b_pty_start_and_write_stdin() -> None: sandbox = _FakeE2BSandbox() sandbox.pty.stdin_output_chunks = [b">>> "] state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) started = await session.pty_exec_start("python3", shell=False, tty=True, yield_time_s=0.05) assert started.process_id is not None assert b">>>" in started.output sandbox.pty.stdin_output_chunks = [b"10\n"] updated = await session.pty_write_stdin( session_id=started.process_id, chars="5 + 5\n", yield_time_s=0.05, ) assert updated.process_id == started.process_id assert b"10" in updated.output assert sandbox.pty.handle.stdin_payloads == [b"python3\n", b"5 + 5\n"] await session.pty_terminate_all() @pytest.mark.asyncio async def test_e2b_pty_start_non_tty_uses_commands_run_in_background() -> None: sandbox = _FakeE2BSandbox() sandbox.commands.async_command_stdout_chunks = ["started\n"] state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) started = await session.pty_exec_start("python3", shell=False, tty=False, yield_time_s=0.05) assert started.process_id is None assert b"started" in started.output assert sandbox.commands.background_calls == [ { "command": "python3", "timeout": float(session.state.timeouts.exec_timeout_unbounded_s), "cwd": "/workspace", "envs": {}, "stdin": False, "background": True, } ] @pytest.mark.asyncio async def test_e2b_pty_start_non_tty_wakes_when_exit_follows_last_output() -> None: sandbox = _FakeE2BSandbox() handle = _FakeE2BAsyncCommandHandle(wait_delay_s=0.01) sandbox.commands.next_async_command_handle = handle sandbox.commands.async_command_stdout_chunks = ["started\n"] state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) started = await asyncio.wait_for( session.pty_exec_start("python3", shell=False, tty=False, yield_time_s=10), timeout=1, ) assert started.process_id is None assert started.exit_code == 0 assert started.output == b"started\n" assert handle.wait_calls == 1 assert handle.kill_calls == 0 @pytest.mark.asyncio async def test_e2b_pty_start_tty_wakes_when_session_exits_after_output() -> None: sandbox = _FakeE2BSandbox() handle = _FakeE2BPtyHandle(wait_never=False, wait_delay_s=0.01) sandbox.pty.handle = handle sandbox.pty.stdin_output_chunks = [b"bye\n"] state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) started = await asyncio.wait_for( session.pty_exec_start("exit", shell=False, tty=True, yield_time_s=10), timeout=1, ) assert started.process_id is None assert started.exit_code == 0 assert started.output == b"bye\n" assert handle.stdin_payloads == [b"exit\n"] assert handle.wait_calls == 1 assert handle.kill_calls == 0 @pytest.mark.asyncio async def test_e2b_pty_start_non_tty_wakes_on_quiet_exit() -> None: sandbox = _FakeE2BSandbox() handle = _FakeE2BAsyncCommandHandle(wait_delay_s=0.01) sandbox.commands.next_async_command_handle = handle state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) started = await asyncio.wait_for( session.pty_exec_start("true", shell=False, tty=False, yield_time_s=10), timeout=1, ) assert started.process_id is None assert started.exit_code == 0 assert started.output == b"" assert handle.wait_calls == 1 assert handle.kill_calls == 0 @pytest.mark.asyncio async def test_e2b_pty_start_non_tty_wakes_on_nonzero_wait_exit() -> None: sandbox = _FakeE2BSandbox() handle = _FakeE2BAsyncCommandHandle( wait_delay_s=0.01, wait_error=_FakeE2BCommandExitException(exit_code=2), ) sandbox.commands.next_async_command_handle = handle state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) started = await asyncio.wait_for( session.pty_exec_start("false", shell=False, tty=False, yield_time_s=10), timeout=1, ) assert started.process_id is None assert started.exit_code == 2 assert started.output == b"" assert handle.wait_calls == 1 assert handle.kill_calls == 0 @pytest.mark.asyncio async def test_e2b_pty_start_non_tty_exited_command_preserves_waiter() -> None: sandbox = _FakeE2BSandbox() handle = _FakeE2BAsyncCommandHandle(initial_exit_code=0, wait_until_released=True) sandbox.commands.next_async_command_handle = handle state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) started = await asyncio.wait_for( session.pty_exec_start("true", shell=False, tty=False, yield_time_s=10), timeout=1, ) assert started.process_id is None assert started.exit_code == 0 assert started.output == b"" assert handle.kill_calls == 0 for _ in range(10): if handle.wait_calls: break await asyncio.sleep(0) assert handle.wait_calls == 1 assert not handle.wait_cancelled handle.release_wait() await asyncio.sleep(0) @pytest.mark.asyncio async def test_e2b_pty_start_non_tty_running_command_cleans_up_waiter() -> None: sandbox = _FakeE2BSandbox() handle = _FakeE2BAsyncCommandHandle(wait_never=True) sandbox.commands.next_async_command_handle = handle state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) started = await session.pty_exec_start("sleep", "60", shell=False, tty=False, yield_time_s=0.01) assert started.process_id is not None assert started.exit_code is None assert handle.wait_calls == 1 assert handle.kill_calls == 0 await session.pty_terminate_all() assert handle.wait_cancelled assert handle.kill_calls == 1 @pytest.mark.asyncio async def test_e2b_pty_start_non_tty_wraps_background_run_failures() -> None: sandbox = _FakeE2BSandbox() sandbox.commands.background_error = RuntimeError("background failed") state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) with pytest.raises(ExecTransportError) as exc_info: await session.pty_exec_start("python3", shell=False, tty=False) assert isinstance(exc_info.value.__cause__, RuntimeError) assert str(exc_info.value.__cause__) == "background failed" @pytest.mark.asyncio async def test_e2b_stop_terminates_live_pty_sessions() -> None: sandbox = _FakeE2BSandbox() state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) started = await session.pty_exec_start("python3", shell=False, tty=True, yield_time_s=0.05) assert started.process_id is not None await session.stop() assert sandbox.pty.handle.exit_code == 0 @pytest.mark.asyncio async def test_e2b_shutdown_logs_pause_failure_and_falls_back_to_kill( caplog: pytest.LogCaptureFixture, ) -> None: sandbox = _FakeE2BSandbox() sandbox.pause_error = RuntimeError("pause failed") state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, pause_on_exit=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) caplog.set_level(logging.WARNING, logger=e2b_module.__name__) await session.shutdown() assert sandbox.pause_calls == 1 assert sandbox.kill_calls == 1 assert "Failed to pause E2B sandbox on shutdown; falling back to kill." in caplog.text @pytest.mark.asyncio async def test_e2b_shutdown_logs_kill_failure_after_pause_fallback( caplog: pytest.LogCaptureFixture, ) -> None: sandbox = _FakeE2BSandbox() sandbox.pause_error = RuntimeError("pause failed") sandbox.kill_error = RuntimeError("kill failed") state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, pause_on_exit=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) caplog.set_level(logging.WARNING, logger=e2b_module.__name__) await session.shutdown() assert sandbox.pause_calls == 1 assert sandbox.kill_calls == 1 assert "Failed to kill E2B sandbox after pause fallback failure." in caplog.text @pytest.mark.asyncio async def test_e2b_shutdown_logs_direct_kill_failure(caplog: pytest.LogCaptureFixture) -> None: sandbox = _FakeE2BSandbox() sandbox.kill_error = RuntimeError("kill failed") state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, pause_on_exit=False, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) caplog.set_level(logging.WARNING, logger=e2b_module.__name__) await session.shutdown() assert sandbox.pause_calls == 0 assert sandbox.kill_calls == 1 assert "Failed to kill E2B sandbox on shutdown." in caplog.text @pytest.mark.asyncio async def test_e2b_pty_start_wraps_startup_failures() -> None: sandbox = _FakeE2BSandbox() sandbox.pty.create_error = FileNotFoundError("missing-shell") state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) with pytest.raises(ExecTransportError): await session.pty_exec_start("python3", shell=False, tty=True) @pytest.mark.asyncio async def test_e2b_pty_start_cleans_up_partially_created_session_on_failure() -> None: sandbox = _FakeE2BSandbox() sandbox.pty.send_stdin_error = RuntimeError("send failed") state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) with pytest.raises(ExecTransportError): await session.pty_exec_start("python3", shell=False, tty=True) assert sandbox.pty.handle.exit_code == 0 @pytest.mark.asyncio async def test_e2b_pty_start_cleans_up_partially_created_session_on_cancellation() -> None: sandbox = _FakeE2BSandbox() sandbox.pty.send_stdin_error = asyncio.CancelledError() state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) with pytest.raises(asyncio.CancelledError): await session.pty_exec_start("python3", shell=False, tty=True) assert sandbox.pty.handle.exit_code == 0 assert session._pty_processes == {} # noqa: SLF001 @pytest.mark.asyncio async def test_e2b_pty_start_maps_timeout_failures( monkeypatch: pytest.MonkeyPatch, ) -> None: sandbox = _FakeE2BSandbox() timeout_error_types = e2b_module._e2b_timeout_error_types() if timeout_error_types: timeout_exc = timeout_error_types[0] else: class _FakeTimeout(Exception): pass timeout_exc = _FakeTimeout monkeypatch.setattr( e2b_module, "_e2b_timeout_error_types", lambda: (_FakeTimeout,), ) sandbox.pty.create_error = timeout_exc("timed out") state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) with pytest.raises(ExecTimeoutError): await session.pty_exec_start("python3", shell=False, tty=True, timeout=2.0) @pytest.mark.asyncio async def test_e2b_exec_timeout_preserves_provider_details( monkeypatch: pytest.MonkeyPatch, ) -> None: class _FakeTimeout(Exception): def __init__(self) -> None: super().__init__("context deadline exceeded") self.stderr = "chrome stderr" sandbox = _FakeE2BSandbox() state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) monkeypatch.setattr( e2b_module, "_e2b_timeout_error_types", lambda: (_FakeTimeout,), ) async def _raise_timeout(*args: object, **kwargs: object) -> object: _ = (args, kwargs) raise _FakeTimeout() monkeypatch.setattr(e2b_module, "_sandbox_run_command", _raise_timeout) with pytest.raises(ExecTimeoutError) as exc_info: await session._exec_internal("python3", "build.py", timeout=2.0) # noqa: SLF001 assert exc_info.value.context["provider_error"] == "context deadline exceeded" assert exc_info.value.context["stderr"] == "chrome stderr" @pytest.mark.asyncio async def test_e2b_exec_maps_httpcore_read_timeout_to_timeout_error( monkeypatch: pytest.MonkeyPatch, ) -> None: class ReadTimeout(Exception): pass ReadTimeout.__module__ = "httpcore" sandbox = _FakeE2BSandbox() state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) async def _raise_timeout(*args: object, **kwargs: object) -> object: _ = (args, kwargs) raise ReadTimeout() monkeypatch.setattr(e2b_module, "_sandbox_run_command", _raise_timeout) with pytest.raises(ExecTimeoutError) as exc_info: await session._exec_internal("python3", "build.py", timeout=2.0) # noqa: SLF001 assert exc_info.value.context["reason"] == "stream_read_timeout" assert exc_info.value.context["provider_error"] == "ReadTimeout" @pytest.mark.asyncio async def test_e2b_exec_maps_missing_sandbox_not_found_to_transport_error( monkeypatch: pytest.MonkeyPatch, ) -> None: class _FakeNotFound(Exception): pass sandbox = _FakeE2BSandbox() state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) monkeypatch.setattr( e2b_module, "_e2b_non_retryable_error_types", lambda: (_FakeNotFound,), ) monkeypatch.setattr(e2b_module, "_e2b_retryable_error_types", lambda: ()) monkeypatch.setattr(e2b_module, "_e2b_timeout_error_types", lambda: ()) async def _raise_not_found(*args: object, **kwargs: object) -> object: _ = (args, kwargs) raise _FakeNotFound("The sandbox was not found: request failed") monkeypatch.setattr(e2b_module, "_sandbox_run_command", _raise_not_found) with pytest.raises(ExecTransportError) as exc_info: await session._exec_internal("python3", "build.py", timeout=2.0) # noqa: SLF001 assert exc_info.value.context["provider_error"] == "The sandbox was not found: request failed" assert exc_info.value.context["reason"] == "_FakeNotFound" assert exc_info.value.retryable is False @pytest.mark.asyncio async def test_e2b_exec_marks_rate_limit_retryable( monkeypatch: pytest.MonkeyPatch, ) -> None: class _FakeRateLimit(Exception): pass sandbox = _FakeE2BSandbox() state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) monkeypatch.setattr(e2b_module, "_e2b_retryable_error_types", lambda: (_FakeRateLimit,)) monkeypatch.setattr(e2b_module, "_e2b_non_retryable_error_types", lambda: ()) monkeypatch.setattr(e2b_module, "_e2b_timeout_error_types", lambda: ()) async def _raise_rate_limit(*args: object, **kwargs: object) -> object: _ = (args, kwargs) raise _FakeRateLimit("rate limit exceeded") monkeypatch.setattr(e2b_module, "_sandbox_run_command", _raise_rate_limit) with pytest.raises(ExecTransportError) as exc_info: await session._exec_internal("python3", "build.py", timeout=2.0) # noqa: SLF001 assert exc_info.value.context["provider_error"] == "rate limit exceeded" assert exc_info.value.context["reason"] == "_FakeRateLimit" assert exc_info.value.retryable is True @pytest.mark.asyncio async def test_e2b_exec_marks_deterministic_provider_errors_non_retryable( monkeypatch: pytest.MonkeyPatch, ) -> None: class _FakeGitAuth(Exception): pass sandbox = _FakeE2BSandbox() state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) monkeypatch.setattr(e2b_module, "_e2b_retryable_error_types", lambda: ()) monkeypatch.setattr(e2b_module, "_e2b_non_retryable_error_types", lambda: (_FakeGitAuth,)) monkeypatch.setattr(e2b_module, "_e2b_timeout_error_types", lambda: ()) async def _raise_git_auth(*args: object, **kwargs: object) -> object: _ = (args, kwargs) raise _FakeGitAuth("git authentication failed") monkeypatch.setattr(e2b_module, "_sandbox_run_command", _raise_git_auth) with pytest.raises(ExecTransportError) as exc_info: await session._exec_internal("python3", "build.py", timeout=2.0) # noqa: SLF001 assert exc_info.value.context["provider_error"] == "git authentication failed" assert exc_info.value.context["reason"] == "_FakeGitAuth" assert exc_info.value.retryable is False @pytest.mark.asyncio async def test_e2b_exec_transport_preserves_provider_details( monkeypatch: pytest.MonkeyPatch, ) -> None: sandbox = _FakeE2BSandbox() state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) async def _raise_transport(*args: object, **kwargs: object) -> object: _ = (args, kwargs) raise RuntimeError("connection closed while reading HTTP status line") monkeypatch.setattr(e2b_module, "_sandbox_run_command", _raise_transport) with pytest.raises(ExecTransportError) as exc_info: await session._exec_internal("python3", "build.py", timeout=2.0) # noqa: SLF001 assert ( exc_info.value.context["provider_error"] == "connection closed while reading HTTP status line" ) @pytest.mark.asyncio async def test_e2b_pty_start_maps_httpcore_read_timeout_to_timeout_error() -> None: class ReadTimeout(Exception): pass ReadTimeout.__module__ = "httpcore" sandbox = _FakeE2BSandbox() sandbox.pty.create_error = ReadTimeout() state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) with pytest.raises(ExecTimeoutError) as exc_info: await session.pty_exec_start("python3", shell=False, tty=True, timeout=2.0) assert exc_info.value.context["reason"] == "stream_read_timeout" assert exc_info.value.context["provider_error"] == "ReadTimeout" @pytest.mark.asyncio async def test_e2b_pty_start_maps_missing_sandbox_not_found_to_transport_error( monkeypatch: pytest.MonkeyPatch, ) -> None: class _FakeNotFound(Exception): pass monkeypatch.setattr( e2b_module, "_e2b_non_retryable_error_types", lambda: (_FakeNotFound,), ) monkeypatch.setattr(e2b_module, "_e2b_retryable_error_types", lambda: ()) monkeypatch.setattr(e2b_module, "_e2b_timeout_error_types", lambda: ()) sandbox = _FakeE2BSandbox() sandbox.pty.create_error = _FakeNotFound("The sandbox was not found: request failed") state = E2BSandboxSessionState( session_id=uuid.uuid4(), manifest=Manifest(root="/workspace"), snapshot=NoopSnapshot(id="snapshot"), sandbox_id=sandbox.sandbox_id, workspace_root_ready=True, ) session = E2BSandboxSession.from_state(state, sandbox=sandbox) with pytest.raises(ExecTransportError) as exc_info: await session.pty_exec_start("python3", shell=False, tty=True, timeout=2.0) assert exc_info.value.context["provider_error"] == "The sandbox was not found: request failed" assert exc_info.value.context["reason"] == "_FakeNotFound" assert exc_info.value.retryable is False