{"record":{"id":"20835630fdc23ccb","repo":"abhigyanpatwari/GitNexus","slug":"task-asset-filesystem-cannot-reflink-the-snapshot","errorCode":null,"errorMessage":"task asset filesystem cannot reflink the snapshot and the buffered fallback limit would be exceeded","messagePattern":"task asset filesystem cannot reflink the snapshot and the buffered fallback limit would be exceeded","errorType":"exception","errorClass":"SandboxError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/task_assets.py","lineNumber":827,"sourceCode":"        raise SandboxError(f\"task asset snapshot file changed: {entry.path}\")\n    temporary = destination.with_name(f\".{destination.name}.{uuid.uuid4().hex}.tmp\")\n    source_descriptor = os.open(source, os.O_RDONLY | getattr(os, \"O_CLOEXEC\", 0) | getattr(os, \"O_NOFOLLOW\", 0))\n    destination_descriptor = os.open(\n        temporary,\n        os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, \"O_CLOEXEC\", 0),\n        0o600,\n    )\n    fallback_bytes = 0\n    try:\n        opened = os.fstat(source_descriptor)\n        if _mutation_identity(opened) != _mutation_identity(metadata):\n            raise SandboxError(f\"task asset snapshot file changed: {entry.path}\")\n        if _try_reflink(source_descriptor, destination_descriptor):\n            if os.fstat(destination_descriptor).st_size != entry.size:\n                raise SandboxError(f\"task asset reflink produced an invalid file: {entry.path}\")\n        else:\n            if entry.size > fallback_budget:\n                raise SandboxError(\n                    \"task asset filesystem cannot reflink the snapshot and the buffered fallback limit would be exceeded\"\n                )\n            os.ftruncate(destination_descriptor, 0)\n            os.lseek(source_descriptor, 0, os.SEEK_SET)\n            while True:\n                chunk = os.read(source_descriptor, COPY_CHUNK_BYTES)\n                if not chunk:\n                    break\n                _write_all(destination_descriptor, chunk)\n                fallback_bytes += len(chunk)\n            if fallback_bytes != entry.size:\n                raise SandboxError(f\"task asset snapshot file changed while materializing: {entry.path}\")\n        if _mutation_identity(opened) != _mutation_identity(os.fstat(source_descriptor)):\n            raise SandboxError(f\"task asset snapshot file changed while materializing: {entry.path}\")\n        os.fchmod(destination_descriptor, 0o600)\n    finally:\n        os.close(destination_descriptor)\n        os.close(source_descriptor)","sourceCodeStart":809,"sourceCodeEnd":845,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/task_assets.py#L809-L845","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Move the cache dir to a reflink-capable filesystem (btrfs or xfs) so the buffered fallback is not needed","If a buffered copy is mandatory, shrink the offending asset below 512 MiB","Avoid stacking many large assets in one snapshot — each reduces the remaining budget"],"exampleFix":"# before: cache on ext4 (no reflink) -> 428 MiB index hits the 512 MiB cap\n#   export WFBENCH_TASK_ASSET_CACHE=/var/cache/wfbench\n# after: cache on btrfs so FICLONE reflink is used (no fallback budget consumed)\n#   mkdir -p /mnt/btrfs/wfbench-cache\n#   export WFBENCH_TASK_ASSET_CACHE=/mnt/btrfs/wfbench-cache","handlingStrategy":"validation","validationCode":"import errno, fcntl, os, tempfile\nfrom pathlib import Path\n\nFICLONE = 0x40049409\nMAX_BUFFERED_FALLBACK_BYTES = 512 * 1024 * 1024\n\ndef cache_supports_reflink(cache_dir: Path) -> bool:\n    a = cache_dir / \".reflink_probe_a\"; b = cache_dir / \".reflink_probe_b\"\n    try:\n        a.write_bytes(b\"x\"); b.touch()\n        fa = os.open(a, os.O_RDONLY); fb = os.open(b, os.O_WRONLY)\n        try:\n            fcntl.ioctl(fb, FICLONE, fa); return True\n        except OSError as exc:\n            return exc.errno not in {errno.EXDEV, errno.EINVAL, errno.ENOTTY, errno.EOPNOTSUPP, errno.ENOSYS}\n        finally:\n            os.close(fa); os.close(fb)\n    finally:\n        a.unlink(missing_ok=True); b.unlink(missing_ok=True)\n\ndef fits_fallback_budget(total_entry_bytes: int) -> bool:\n    return total_entry_bytes <= MAX_BUFFERED_FALLBACK_BYTES","typeGuard":null,"tryCatchPattern":"from eval.workflow_bench.proposer_sandbox import SandboxError\n\ntry:\n    snapshot.materialize(clone)\nexcept SandboxError as exc:\n    if \"cannot reflink\" in str(exc):\n        raise SystemExit(f\"move the cache to a reflink-capable fs (btrfs/xfs) or shrink the asset: {exc}\") from exc\n    raise","preventionTips":["Place TaskAssetCache.root on a reflink-capable filesystem (btrfs or xfs) so FICLONE succeeds and the buffered fallback is never used","Keep individual sandbox_copy assets below 512 MiB, and the cumulative total below the same cap, for non-reflink filesystems","Probe reflink support on the cache dir at startup and fail fast if unsupported with oversized assets"],"tags":["sandbox","reflink","filesystem","configuration"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}