From 13a611069c1ae5c50cee47eee5a31a1ffceea491 Mon Sep 17 00:00:00 2001 From: Salma Date: Fri, 23 Oct 2026 02:51:34 +0530 Subject: [PATCH] fix(pi): restore a snapshot of an empty workspace _unpack extracted the workspace archive into a staging directory and then iterated it, but an empty workspace's archive has no members, so extractall never created the directory and iterdir raised FileNotFoundError: loading the snapshot failed with HTTP 500 after the existing workspace contents had already been removed. Create the staging directory before extracting, as the OpenHands agent does. Closes #285 Co-Authored-By: Claude Opus 5.5 --- src/agent_env/agents/agents/pi/pi_agent.py | 2 ++ tst/unit/agents/pi/test_pi_agent.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/agent_env/agents/agents/pi/pi_agent.py b/src/agent_env/agents/agents/pi/pi_agent.py index e024703f..9c9b70a8 100644 --- a/src/agent_env/agents/agents/pi/pi_agent.py +++ b/src/agent_env/agents/agents/pi/pi_agent.py @@ -335,6 +335,8 @@ def _unpack(archive: Path, directory: Path) -> None: inside it first, so one that fails to extract leaves the contents as they were.""" staging = directory / f".pi-a2a-restore-{uuid.uuid4().hex}" try: + # An empty workspace's archive has no members, so extraction creates nothing. + staging.mkdir() with tarfile.open(archive) as tar: tar.extractall(staging, filter="data") for child in directory.iterdir(): diff --git a/tst/unit/agents/pi/test_pi_agent.py b/tst/unit/agents/pi/test_pi_agent.py index 01be125c..3238cc3f 100644 --- a/tst/unit/agents/pi/test_pi_agent.py +++ b/tst/unit/agents/pi/test_pi_agent.py @@ -332,6 +332,20 @@ def test_a_workspace_archive_that_fails_to_extract_changes_nothing(tmp_path: Pat assert [path.name for path in workspace.iterdir()] == ["keep.txt"] +def test_an_empty_workspace_archive_restores_an_empty_workspace(tmp_path: Path) -> None: + empty = tmp_path / "empty" + empty.mkdir() + archive = tmp_path / "workspace.tar.gz" + pi_agent._pack(empty, archive) + workspace = tmp_path / "ws" + workspace.mkdir() + (workspace / "later.txt").write_text("written after the snapshot") + + pi_agent._unpack(archive, workspace) + + assert list(workspace.iterdir()) == [] + + def test_restoring_the_workspace_waits_for_no_running_task( tmp_path: Path, record: Path, objects: dict[str, bytes] ) -> None: