abhigyanpatwari/GitNexus · error · SandboxError

task asset filesystem cannot reflink the snapshot and the bu

Error message

task asset filesystem cannot reflink the snapshot and the buffered fallback limit would be exceeded

What it means

When _try_reflink returns False (reflink unsupported — ext4, 9p, tmpfs, or cross-device; errnos EXDEV/EINVAL/ENOTTY/EOPNOTSUPP/ENOSYS) AND the entry size exceeds the remaining buffered-fallback budget (capped at MAX_BUFFERED_FALLBACK_BYTES = 512 MiB cumulative across all entries in the snapshot), materialization is aborted to avoid an unbounded buffered copy. This is a configuration/environment error, not a security violation.

Source

Thrown at eval/workflow_bench/task_assets.py:827

        raise SandboxError(f"task asset snapshot file changed: {entry.path}")
    temporary = destination.with_name(f".{destination.name}.{uuid.uuid4().hex}.tmp")
    source_descriptor = os.open(source, os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0))
    destination_descriptor = os.open(
        temporary,
        os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_CLOEXEC", 0),
        0o600,
    )
    fallback_bytes = 0
    try:
        opened = os.fstat(source_descriptor)
        if _mutation_identity(opened) != _mutation_identity(metadata):
            raise SandboxError(f"task asset snapshot file changed: {entry.path}")
        if _try_reflink(source_descriptor, destination_descriptor):
            if os.fstat(destination_descriptor).st_size != entry.size:
                raise SandboxError(f"task asset reflink produced an invalid file: {entry.path}")
        else:
            if entry.size > fallback_budget:
                raise SandboxError(
                    "task asset filesystem cannot reflink the snapshot and the buffered fallback limit would be exceeded"
                )
            os.ftruncate(destination_descriptor, 0)
            os.lseek(source_descriptor, 0, os.SEEK_SET)
            while True:
                chunk = os.read(source_descriptor, COPY_CHUNK_BYTES)
                if not chunk:
                    break
                _write_all(destination_descriptor, chunk)
                fallback_bytes += len(chunk)
            if fallback_bytes != entry.size:
                raise SandboxError(f"task asset snapshot file changed while materializing: {entry.path}")
        if _mutation_identity(opened) != _mutation_identity(os.fstat(source_descriptor)):
            raise SandboxError(f"task asset snapshot file changed while materializing: {entry.path}")
        os.fchmod(destination_descriptor, 0o600)
    finally:
        os.close(destination_descriptor)
        os.close(source_descriptor)

View on GitHub (pinned to d540b00184)

Solutions

  1. Move the cache dir to a reflink-capable filesystem (btrfs or xfs) so the buffered fallback is not needed
  2. If a buffered copy is mandatory, shrink the offending asset below 512 MiB
  3. Avoid stacking many large assets in one snapshot — each reduces the remaining budget

Example fix

# before: cache on ext4 (no reflink) -> 428 MiB index hits the 512 MiB cap
#   export WFBENCH_TASK_ASSET_CACHE=/var/cache/wfbench
# after: cache on btrfs so FICLONE reflink is used (no fallback budget consumed)
#   mkdir -p /mnt/btrfs/wfbench-cache
#   export WFBENCH_TASK_ASSET_CACHE=/mnt/btrfs/wfbench-cache
Defensive patterns

Strategy: validation

Validate before calling

import errno, fcntl, os, tempfile
from pathlib import Path

FICLONE = 0x40049409
MAX_BUFFERED_FALLBACK_BYTES = 512 * 1024 * 1024

def cache_supports_reflink(cache_dir: Path) -> bool:
    a = cache_dir / ".reflink_probe_a"; b = cache_dir / ".reflink_probe_b"
    try:
        a.write_bytes(b"x"); b.touch()
        fa = os.open(a, os.O_RDONLY); fb = os.open(b, os.O_WRONLY)
        try:
            fcntl.ioctl(fb, FICLONE, fa); return True
        except OSError as exc:
            return exc.errno not in {errno.EXDEV, errno.EINVAL, errno.ENOTTY, errno.EOPNOTSUPP, errno.ENOSYS}
        finally:
            os.close(fa); os.close(fb)
    finally:
        a.unlink(missing_ok=True); b.unlink(missing_ok=True)

def fits_fallback_budget(total_entry_bytes: int) -> bool:
    return total_entry_bytes <= MAX_BUFFERED_FALLBACK_BYTES

Try / catch

from eval.workflow_bench.proposer_sandbox import SandboxError

try:
    snapshot.materialize(clone)
except SandboxError as exc:
    if "cannot reflink" in str(exc):
        raise SystemExit(f"move the cache to a reflink-capable fs (btrfs/xfs) or shrink the asset: {exc}") from exc
    raise

Prevention

When it happens

Trigger: Running on a filesystem without reflink support (ext4 CI runner, 9p-backed dev mount) with a sandbox_copy asset larger than 512 MiB; or cumulative assets draining the shared budget.

Common situations: CI on ext4 with a large GitNexus index (~428 MiB shipped, growing); Docker dev mount over 9p; the shipped index grew past the budget after a release.

Related errors


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