abhigyanpatwari/GitNexus · error · SandboxError

task asset snapshot does not match this task declaration

Error message

task asset snapshot does not match this task declaration

What it means

Raised by stage_task_assets when a supplied snapshot's repo_identity (real directory path of the repo) or sandbox_copy declarations do not match the current task/repo. The snapshot is provenance-bound: it must have been captured from the same real repo directory and the same sandbox_copy declaration set, otherwise its frozen bytes cannot be trusted to represent this task's inputs.

Source

Thrown at eval/workflow_bench/task_assets.py:1075

def stage_task_assets(
    task: Mapping[str, Any],
    *,
    repo: Path,
    clone: Path,
    snapshot: TaskAssetSnapshot | None = None,
) -> list[ReadOnlyMount]:
    """Materialize copied assets and validate read-only dependency mounts.

    ``snapshot`` is supplied by the benchmark runner so every arm reuses one
    capture.  The optional path preserves the historic standalone helper API
    for containment tests and external callers.
    """

    repo_identity = _real_directory(repo, label="task asset repository")
    declarations, _ = _sandbox_copy_declarations(task)
    if snapshot is not None:
        if snapshot.repo_identity != repo_identity or snapshot.declarations != declarations:
            raise SandboxError("task asset snapshot does not match this task declaration")
        snapshot.materialize(clone)
        return _dependency_mounts(task, clone=clone, snapshot=snapshot)

    if _sandbox_dependency_declarations(task):
        raise SandboxError("sandbox_dependencies require a caller-owned immutable task asset snapshot")

    with tempfile.TemporaryDirectory(prefix="wfbench-asset-snapshot-") as temporary:
        with TaskAssetCache(Path(temporary) / "cache") as cache:
            ephemeral = cache.prepare(task, repo=repo_identity, resolved_sha="unbound")
            ephemeral.materialize(clone)
    return []

View on GitHub (pinned to d540b00184)

Solutions

  1. Build the snapshot and stage from it in the same run, passing the identical repo Path to both cache.prepare and stage_task_assets (resolve symlinks consistently — _real_directory is used both sides).
  2. After editing sandbox_copy in a task, call cache.prepare again to capture a fresh snapshot; do not reuse the old one.
  3. Key any snapshot cache by (repo_identity, resolved_sha, sandbox_copy declarations, sandbox_dependencies) and discard entries that no longer match.

Example fix

# before: snapshot built once, reused after sandbox_copy changed
snapshot = cache.prepare(task, repo=repo, resolved_sha=sha)
task['sandbox_copy'].append('build/out')     # declaration mutated
stage_task_assets(task, repo=repo, clone=clone, snapshot=snapshot)  # -> mismatch

# after: rebuild the snapshot whenever the task declaration changes
task['sandbox_copy'].append('build/out')
snapshot = cache.prepare(task, repo=repo, resolved_sha=sha)
stage_task_assets(task, repo=repo, clone=clone, snapshot=snapshot)
Defensive patterns

Strategy: validation

Validate before calling

from .task_assets import _sandbox_copy_declarations, _real_directory

def assert_snapshot_matches_task(task, *, repo, snapshot) -> None:
    repo_identity = _real_directory(repo, label='task asset repository')
    declarations, _ = _sandbox_copy_declarations(task)
    if snapshot.repo_identity != repo_identity or snapshot.declarations != declarations:
        raise ValueError(
            'snapshot provenance does not match task/repo; rebuild via cache.prepare.'
        )

# Call before stage_task_assets; rebuild if mismatched.

Type guard

def snapshot_matches_task(task, *, repo, snapshot) -> bool:
    repo_identity = _real_directory(repo, label='task asset repository')
    declarations, _ = _sandbox_copy_declarations(task)
    return snapshot.repo_identity == repo_identity and snapshot.declarations == declarations

Try / catch

from .proposer_sandbox import SandboxError

try:
    stage_task_assets(task, repo=repo, clone=clone, snapshot=snapshot)
except SandboxError as exc:
    if 'does not match this task declaration' in str(exc):
        with TaskAssetCache(cache_dir) as cache:
            snapshot = cache.prepare(task, repo=_real_directory(repo, label='task asset repository'), resolved_sha=sha)
        stage_task_assets(task, repo=repo, clone=clone, snapshot=snapshot)
    else:
        raise

Prevention

When it happens

Trigger: Passing a snapshot built from repo A into a stage call for repo B; reusing a snapshot after the sandbox_copy declaration was edited; passing a snapshot whose repo_identity was resolved via a symlink while stage resolves a different real path (or vice versa); snapshot built with resolved_sha='unbound' for a task that now needs a bound snapshot.

Common situations: Caching a snapshot across CI runs and reusing it after the repo path changed (different workdir); editing sandbox_copy between prepare and stage; resolving repo via a symlink in one path and the realpath in another; mixing the ephemeral standalone snapshot path with the benchmark runner's caller-owned snapshot.

Related errors


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