abhigyanpatwari/GitNexus · error · SandboxError

sandbox_dependencies require a caller-owned immutable task a

Error message

sandbox_dependencies require a caller-owned immutable task asset snapshot

What it means

Raised by stage_task_assets when snapshot is None but the task declares non-empty sandbox_dependencies. Dependency mounts require a caller-owned, immutable TaskAssetSnapshot (captured once by the benchmark runner and reused across arms) so that every arm sees identical, frozen dependency bytes; an ad-hoc ephemeral capture inside stage_task_assets cannot provide that guarantee for dependencies, so the call is rejected rather than silently doing the wrong thing.

Source

Thrown at eval/workflow_bench/task_assets.py:1080

    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 and pass a snapshot: with TaskAssetCache(cache_dir) as cache: snapshot = cache.prepare(task, repo=repo, resolved_sha=sha); then stage_task_assets(task, repo=repo, clone=clone, snapshot=snapshot).
  2. Use capture_task_dependency_binding first if you only need the dependency binding digests, then prepare the full snapshot for staging.
  3. If you do not need dependencies, remove the sandbox_dependencies key from the task so the ephemeral-snapshot path applies.

Example fix

# before: dependencies declared but no snapshot supplied
stage_task_assets(
    {'sandbox_copy': [...], 'sandbox_dependencies': [{'source': 'x', 'target': 'y'}]},
    repo=repo, clone=clone,
)   # -> raises

# after: prepare a caller-owned snapshot and pass it through
with TaskAssetCache(cache_dir) as cache:
    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_dependency_declarations, TaskAssetCache

def stage_safely(task, *, repo, clone, cache_dir, resolved_sha):
    if _sandbox_dependency_declarations(task):
        with TaskAssetCache(cache_dir) as cache:
            snapshot = cache.prepare(task, repo=repo, resolved_sha=resolved_sha)
    else:
        snapshot = None   # sandbox_copy-only: ephemeral snapshot path is fine
    return stage_task_assets(task, repo=repo, clone=clone, snapshot=snapshot)

# Always build a caller-owned snapshot when the task declares sandbox_dependencies.

Type guard

def needs_caller_snapshot(task) -> bool:
    return bool(_sandbox_dependency_declarations(task))

Try / catch

from .proposer_sandbox import SandboxError

try:
    stage_task_assets(task, repo=repo, clone=clone)   # no snapshot
except SandboxError as exc:
    if 'require a caller-owned immutable task asset snapshot' in str(exc):
        with TaskAssetCache(cache_dir) as cache:
            snapshot = cache.prepare(task, repo=repo, resolved_sha=sha)
        stage_task_assets(task, repo=repo, clone=clone, snapshot=snapshot)
    else:
        raise

Prevention

When it happens

Trigger: Calling stage_task_assets(task, repo=..., clone=...) without a snapshot argument on a task whose sandbox_dependencies list is non-empty. The fallback ephemeral-snapshot branch (for sandbox_copy-only standalone callers) does not support dependencies.

Common situations: Standalone/containment test calling stage_task_assets directly without first building a snapshot via TaskAssetCache; refactoring a caller to use dependencies but forgetting to plumb the snapshot through; benchmark runner path bypassed by an external script.

Related errors


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