abhigyanpatwari/GitNexus · error · SandboxError

dependency snapshot changed: {dependency.source}

Error message

dependency snapshot changed: {dependency.source}

What it means

In TaskAssetSnapshot.dependency_mounts, each dependency's captured source under the frozen snapshot root must still be the expected type (directory or regular file) and must not be a symlink. If lstat disagrees with what was captured, the snapshot was mutated and the mount is refused.

Source

Thrown at eval/workflow_bench/task_assets.py:170

        finally:
            shutil.rmtree(staging, ignore_errors=True)

    def dependency_mounts(self, clone: Path) -> list[ReadOnlyMount]:
        """Mount only immutable captured dependency roots into an arm clone."""

        clone = _real_directory(clone, label="dependency clone")
        snapshot_root = _real_directory(self.root, label="task asset snapshot")
        mounts: list[ReadOnlyMount] = []
        for dependency in self.dependencies:
            source = snapshot_root / Path(*dependency.snapshot_path.parts)
            metadata = source.lstat()
            expected_directory = dependency.kind == "directory"
            if (
                stat.S_ISLNK(metadata.st_mode)
                or (expected_directory and not stat.S_ISDIR(metadata.st_mode))
                or (not expected_directory and not stat.S_ISREG(metadata.st_mode))
            ):
                raise SandboxError(f"dependency snapshot changed: {dependency.source}")
            target = PurePosixPath(dependency.target)
            _prepare_clone_target(
                clone,
                target,
                directory=expected_directory,
                label="dependency",
            )
            mounts.append(
                ReadOnlyMount(
                    source=source,
                    target=f"{SANDBOX_WORKSPACE}/{target.as_posix()}",
                )
            )
        return mounts


class TaskAssetCache:
    """Own immutable snapshots for one benchmark invocation."""

View on GitHub (pinned to d540b00184)

Solutions

  1. Confirm _freeze_snapshot ran on the snapshot and the tree is mode 0500/0400.
  2. Ensure the cache root is single-writer and not shared across benchmark processes that could mutate it.
  3. Re-capture the snapshot (drop the cache entry) so it is freshly frozen.
Defensive patterns

Strategy: validation

Validate before calling

import os, stat
from pathlib import Path

def assert_dependency_snapshot_intact(snapshot_root, dependencies):
    for dep in dependencies:
        src = Path(snapshot_root, *dep.snapshot_path.parts)
        m = src.lstat()
        expected_dir = dep.kind == "directory"
        if stat.S_ISLNK(m.st_mode):
            raise RuntimeError(f"dependency snapshot became a symlink: {dep.source}")
        if expected_dir and not stat.S_ISDIR(m.st_mode):
            raise RuntimeError(f"dependency snapshot not a directory: {dep.source}")
        if not expected_dir and not stat.S_ISREG(m.st_mode):
            raise RuntimeError(f"dependency snapshot not a regular file: {dep.source}")

Prevention

When it happens

Trigger: Between snapshot capture and dependency_mounts (called per arm), the frozen snapshot's dependency path became a symlink or changed type - e.g. _freeze_snapshot did not actually lock it, or an external process mutated the nominally-immutable tree.

Common situations: A bug in freeze/thaw leaving the snapshot writable; concurrent arms racing on a shared cache root; external tampering with the cache directory.

Related errors


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