diff --git a/agent_builder/tools.py b/agent_builder/tools.py index 880c2d8..b421021 100644 --- a/agent_builder/tools.py +++ b/agent_builder/tools.py @@ -764,16 +764,15 @@ def _safe_supporting_skill_path(path: str) -> str: def _delete_workspace_objects(s3: Any, bucket: str, prefix: str) -> None: + # MinIO rejects boto3's batch DeleteObjects request in this environment + # when Content-MD5 is not present. Per-object deletes avoid that fragile + # API path and are fine for agent source workspaces. paginator = s3.get_paginator("list_objects_v2") - batch: list[dict[str, str]] = [] for page in paginator.paginate(Bucket=bucket, Prefix=prefix): for obj in page.get("Contents") or (): - batch.append({"Key": obj["Key"]}) - if len(batch) >= 1000: - s3.delete_objects(Bucket=bucket, Delete={"Objects": batch}) - batch = [] - if batch: - s3.delete_objects(Bucket=bucket, Delete={"Objects": batch}) + key = obj.get("Key") + if key: + s3.delete_object(Bucket=bucket, Key=key) def _files_from_tarball(bundle: bytes) -> dict[str, bytes]: diff --git a/tests/test_template_init.py b/tests/test_template_init.py index c58923e..7c07688 100644 --- a/tests/test_template_init.py +++ b/tests/test_template_init.py @@ -192,9 +192,11 @@ class _FakeS3: ) -> None: self.objects[Key] = bytes(Body) + def delete_object(self, *, Bucket: str, Key: str) -> None: + self.objects.pop(Key, None) + def delete_objects(self, *, Bucket: str, Delete: dict[str, object]) -> None: - for obj in Delete["Objects"]: # type: ignore[index] - self.objects.pop(obj["Key"], None) + raise AssertionError("batch DeleteObjects should not be used") class _FakePaginator: