Skip to content

GitRepo sandbox artifact leaves temporary clone after copy failure #3170

@Aphroq

Description

@Aphroq

Please read this first

  • Have you read the docs? Yes. I checked the sandbox artifact implementation and existing GitRepo tests.
  • Have you searched for related issues? Yes. I searched issues/PRs.

Describe the bug

GitRepo.apply() creates a temporary clone directory under /tmp/sandbox-git-... inside the sandbox container. The successful path removes this temporary directory after copying the repo contents into the destination, but failures after a successful clone can skip cleanup.

For example, if cp -R fails because the requested subpath is missing or the destination copy fails, GitCopyError is raised before the final cleanup command runs. This can leave cloned repository contents behind in /tmp, including private repository content, and can also grow sandbox disk usage over repeated failures.

Debug information

  • Agents SDK version: 0.16.0
  • Source revision tested: eed9100
  • Python version: Python 3.12.1

Repro steps

Run this from the repository root on current main:

import asyncio
from pathlib import Path

from agents.sandbox.entries import GitRepo
from agents.sandbox.errors import GitCopyError
from agents.sandbox.types import ExecResult
from tests.sandbox.test_entries import _RecordingSession


class CopyFailSession(_RecordingSession):
    async def _exec_internal(self, *command, timeout=None):
        cmd = tuple(str(part) for part in command)
        self.exec_calls.append(cmd)
        if cmd == ("command -v git >/dev/null 2>&1",):
            return ExecResult(stdout=b"/usr/bin/git\n", stderr=b"", exit_code=0)
        if cmd[:1] == ("cp",):
            return ExecResult(stdout=b"", stderr=b"copy failed", exit_code=1)
        return ExecResult(stdout=b"", stderr=b"", exit_code=0)


async def main() -> None:
    session = CopyFailSession()
    try:
        await GitRepo(repo="openai/example", ref="main").apply(
            session,
            Path("/workspace/repo"),
            Path("/ignored"),
        )
    except GitCopyError:
        print("copy_error: True")

    cleanup_calls = [call for call in session.exec_calls if call[:3] == ("rm", "-rf", "--")]
    print("cleanup_calls:", len(cleanup_calls))
    print("cleanup_commands:", cleanup_calls)


asyncio.run(main())

Actual output:

copy_error: True
cleanup_calls: 1
cleanup_commands: [('rm', '-rf', '--', '/tmp/sandbox-git-...')]

The single cleanup call is the pre-clone cleanup before the temporary path is created. There is no cleanup after the failed copy.

Expected behavior

GitRepo.apply() should remove the temporary clone directory in a finally block after clone/fetch starts, so cleanup runs even if clone, mkdir/copy, subpath copy, or metadata-adjacent operations fail.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions