From fef9217b111100a8ad7d93ca3d24c0aa912623b6 Mon Sep 17 00:00:00 2001 From: rome-xi Date: Mon, 17 Aug 2026 17:27:43 +0000 Subject: [PATCH] fix(sandbox): make typecheck pass on Windows Signed-off-by: rome-xi --- src/agents/sandbox/sandboxes/docker.py | 2 +- src/agents/sandbox/sandboxes/unix_local.py | 4 +- src/agents/sandbox/util/tar_utils.py | 3 +- tests/sandbox/test_windows_tempfile_iobase.py | 40 +++++++++++++++++++ 4 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 tests/sandbox/test_windows_tempfile_iobase.py diff --git a/src/agents/sandbox/sandboxes/docker.py b/src/agents/sandbox/sandboxes/docker.py index fd9ebbe556..70ce1a96da 100644 --- a/src/agents/sandbox/sandboxes/docker.py +++ b/src/agents/sandbox/sandboxes/docker.py @@ -1427,7 +1427,7 @@ async def hydrate_workspace(self, data: io.IOBase) -> None: archive.seek(0) await self._stream_into_exec( cmd=["tar", "-x", "-C", root.as_posix()], - stream=archive, + stream=cast(io.IOBase, archive), error_path=error_root, ) diff --git a/src/agents/sandbox/sandboxes/unix_local.py b/src/agents/sandbox/sandboxes/unix_local.py index 4d8595b15e..d0c2ea28b7 100644 --- a/src/agents/sandbox/sandboxes/unix_local.py +++ b/src/agents/sandbox/sandboxes/unix_local.py @@ -336,8 +336,8 @@ def _preexec() -> None: else: with suppress(OSError): os.close(secondary_fd) - entry = _UnixPtyProcessEntry(process=process, tty=True, primary_fd=primary_fd) - entry.pump_tasks = [asyncio.create_task(self._pump_pty_primary_fd(entry))] + entry = _UnixPtyProcessEntry(process=process, tty=True, primary_fd=primary_fd) + entry.pump_tasks = [asyncio.create_task(self._pump_pty_primary_fd(entry))] else: process = await asyncio.create_subprocess_exec( *exec_command, diff --git a/src/agents/sandbox/util/tar_utils.py b/src/agents/sandbox/util/tar_utils.py index cf3c4595ac..55adbd77e4 100644 --- a/src/agents/sandbox/util/tar_utils.py +++ b/src/agents/sandbox/util/tar_utils.py @@ -8,6 +8,7 @@ import tempfile from collections.abc import Iterable from pathlib import Path, PurePosixPath, PureWindowsPath +from typing import cast class UnsafeTarMemberError(ValueError): @@ -158,7 +159,7 @@ def strip_tar_member_prefix(data: io.IOBase, *, prefix: str | Path) -> io.IOBase with tarfile.open(fileobj=out, mode="r:*") as tar: validate_tarfile(tar) out.seek(0) - return out + return cast(io.IOBase, out) except Exception: out.close() raise diff --git a/tests/sandbox/test_windows_tempfile_iobase.py b/tests/sandbox/test_windows_tempfile_iobase.py new file mode 100644 index 0000000000..fd71d6a53e --- /dev/null +++ b/tests/sandbox/test_windows_tempfile_iobase.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +import io +import tarfile +import tempfile + +from agents.sandbox.util.tar_utils import strip_tar_member_prefix + + +def _prefixed_workspace_tar() -> io.BytesIO: + buf = io.BytesIO() + with tarfile.open(fileobj=buf, mode="w") as tar: + directory = tarfile.TarInfo("workspace") + directory.type = tarfile.DIRTYPE + tar.addfile(directory) + payload = b"hello" + member = tarfile.TarInfo("workspace/hello.txt") + member.size = len(payload) + tar.addfile(member, io.BytesIO(payload)) + buf.seek(0) + return buf + + +def test_strip_tar_member_prefix_returns_seekable_stream() -> None: + """Windows TemporaryFile wrappers must remain usable as binary IO streams.""" + + stream = strip_tar_member_prefix(_prefixed_workspace_tar(), prefix="workspace") + stream.seek(0) + with tarfile.open(fileobj=stream, mode="r:*") as tar: + assert tar.getnames() == [".", "hello.txt"] + + +def test_temporary_file_supports_hydrate_style_copy() -> None: + """Docker hydrate copies the archive through TemporaryFile, then seeks it.""" + + payload = b"ustar-payload" + with tempfile.TemporaryFile() as archive: + archive.write(payload) + archive.seek(0) + assert archive.read() == payload