abhigyanpatwari/GitNexus · critical · SandboxError

task asset snapshot file changed: {entry.path}

Error message

task asset snapshot file changed: {entry.path}

What it means

_materialize_file first lstat's the source snapshot file and requires: not a symlink, is a regular file, and st_size equals entry.size from the manifest. Any deviation aborts — the snapshot must be byte-identical to what was captured. Indicates the captured snapshot file was replaced, truncated, extended, or symlinked between capture and materialize, or that entry.size in the manifest is stale.

Source

Thrown at eval/workflow_bench/task_assets.py:809

            relative.name,
            src_dir_fd=source_parent,
            dst_dir_fd=destination_parent,
        )
    finally:
        os.close(destination_parent)
        os.close(source_parent)


def _materialize_file(
    source: Path,
    destination: Path,
    entry: AssetManifestEntry,
    *,
    fallback_budget: int,
) -> int:
    metadata = source.lstat()
    if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISREG(metadata.st_mode) or metadata.st_size != entry.size:
        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(

View on GitHub (pinned to d540b00184)

Solutions

  1. Confirm nothing writes to the TaskAssetCache.root directory during or between runs
  2. Delete the cache entry and re-prepare the snapshot from scratch
  3. Verify entry.size matches os.lstat(source).st_size to detect a stale manifest
Defensive patterns

Strategy: validation

Validate before calling

import os, stat
from pathlib import Path

def snapshot_files_match_manifest(snapshot_root: Path, entries) -> list[str]:
    bad = []
    root = snapshot_root / "sandbox-copy"
    for e in entries:
        if e.get("kind") != "file":
            continue
        p = root / Path(*PurePosixPath(e["path"]).parts)
        try:
            m = p.lstat()
        except OSError as exc:
            bad.append(f"{e['path']}: {exc}"); continue
        if stat.S_ISLNK(m.st_mode) or not stat.S_ISREG(m.st_mode) or m.st_size != e["size"]:
            bad.append(e["path"])
    return bad

Try / catch

from eval.workflow_bench.proposer_sandbox import SandboxError

try:
    snapshot.materialize(clone)
except SandboxError as exc:
    if "snapshot file changed" in str(exc):
        raise SystemExit(f"snapshot mutated since capture; delete the cache entry and re-prepare: {exc}") from exc
    raise

Prevention

When it happens

Trigger: The captured snapshot file was mutated after capture; the manifest size field does not match the on-disk file; the snapshot root was replaced or corrupted.

Common situations: Two arms sharing the snapshot root with one mutating it (the snapshot is meant to be immutable); disk corruption; an external process (backup, AV) touching the cache dir.

Related errors


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