abhigyanpatwari/GitNexus · critical · SandboxError

sandbox_copy path changed while opening: {relative}

Error message

sandbox_copy path changed while opening: {relative}

What it means

_open_child compares (st_dev, st_ino, S_IFMT) from the pre-open stat against the post-open fstat. A mismatch means the directory entry was substituted (rename, or unlink+recreate) during the open window — a classic TOCTOU swap that the containment contract refuses to silently accept.

Source

Thrown at eval/workflow_bench/task_assets.py:658

    try:
        descriptor = os.open(name, flags, dir_fd=parent_descriptor)
    except OSError as exc:
        raise SandboxError(f"sandbox_copy path changed or is unreadable: {relative}: {exc}") from exc
    opened = os.fstat(descriptor)
    if not (stat.S_ISDIR(opened.st_mode) or stat.S_ISREG(opened.st_mode)):
        os.close(descriptor)
        raise SandboxError(f"sandbox_copy accepts only regular files and directories: {relative}")
    if (
        opened.st_dev,
        opened.st_ino,
        stat.S_IFMT(opened.st_mode),
    ) != (
        metadata.st_dev,
        metadata.st_ino,
        stat.S_IFMT(metadata.st_mode),
    ):
        os.close(descriptor)
        raise SandboxError(f"sandbox_copy path changed while opening: {relative}")
    return descriptor


def _validate_dependency_symlinks(
    container: Path,
    entries: tuple[AssetManifestEntry, ...],
    *,
    mount_target: PurePosixPath,
) -> None:
    snapshot_boundary = (container / "payload").resolve(strict=True)
    manifest_boundary = PurePosixPath("payload")
    sandbox_boundary = PurePosixPath(SANDBOX_WORKSPACE)
    sandbox_mount = sandbox_boundary / mount_target
    for entry in entries:
        if entry.kind != "symlink":
            continue
        target = PurePosixPath(entry.link_target)
        relative_to_payload = entry.path.relative_to(manifest_boundary)

View on GitHub (pinned to d540b00184)

Solutions

  1. Freeze the worktree (read-only bind mount, or a dedicated clone per run)
  2. Re-run capture after quiescing all writers
  3. Use one private worktree per concurrent arm so no cross-arm mutation can occur
Defensive patterns

Strategy: try-catch

Try / catch

from eval.workflow_bench.proposer_sandbox import SandboxError

try:
    snapshot = cache.prepare(task, repo=repo, resolved_sha=sha)
except SandboxError as exc:
    if "path changed while opening" in str(exc):
        raise SystemExit(f"TOCTOU inode swap during capture; freeze the worktree: {exc}") from exc
    raise

Prevention

When it happens

Trigger: A component is swapped between stat and open; concurrent git checkout, rsync, or build tooling rewriting the tree during capture.

Common situations: Live worktree edits during benchmarking; a worktree shared across arms without coordination.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12). Data as JSON: /api/errors/9336375b5864e98b. Report an issue: GitHub.