{"record":{"id":"6c8fd1447d34b591","repo":"abhigyanpatwari/GitNexus","slug":"sandbox-dependencies-require-a-caller-owned-immuta","errorCode":null,"errorMessage":"sandbox_dependencies require a caller-owned immutable task asset snapshot","messagePattern":"sandbox_dependencies require a caller-owned immutable task asset snapshot","errorType":"exception","errorClass":"SandboxError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/task_assets.py","lineNumber":1080,"sourceCode":"    snapshot: TaskAssetSnapshot | None = None,\n) -> list[ReadOnlyMount]:\n    \"\"\"Materialize copied assets and validate read-only dependency mounts.\n\n    ``snapshot`` is supplied by the benchmark runner so every arm reuses one\n    capture.  The optional path preserves the historic standalone helper API\n    for containment tests and external callers.\n    \"\"\"\n\n    repo_identity = _real_directory(repo, label=\"task asset repository\")\n    declarations, _ = _sandbox_copy_declarations(task)\n    if snapshot is not None:\n        if snapshot.repo_identity != repo_identity or snapshot.declarations != declarations:\n            raise SandboxError(\"task asset snapshot does not match this task declaration\")\n        snapshot.materialize(clone)\n        return _dependency_mounts(task, clone=clone, snapshot=snapshot)\n\n    if _sandbox_dependency_declarations(task):\n        raise SandboxError(\"sandbox_dependencies require a caller-owned immutable task asset snapshot\")\n\n    with tempfile.TemporaryDirectory(prefix=\"wfbench-asset-snapshot-\") as temporary:\n        with TaskAssetCache(Path(temporary) / \"cache\") as cache:\n            ephemeral = cache.prepare(task, repo=repo_identity, resolved_sha=\"unbound\")\n            ephemeral.materialize(clone)\n    return []\n","sourceCodeStart":1062,"sourceCodeEnd":1087,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/task_assets.py#L1062-L1087","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","Use capture_task_dependency_binding first if you only need the dependency binding digests, then prepare the full snapshot for staging.","If you do not need dependencies, remove the sandbox_dependencies key from the task so the ephemeral-snapshot path applies."],"exampleFix":"# before: dependencies declared but no snapshot supplied\nstage_task_assets(\n    {'sandbox_copy': [...], 'sandbox_dependencies': [{'source': 'x', 'target': 'y'}]},\n    repo=repo, clone=clone,\n)   # -> raises\n\n# after: prepare a caller-owned snapshot and pass it through\nwith TaskAssetCache(cache_dir) as cache:\n    snapshot = cache.prepare(task, repo=repo, resolved_sha=sha)\nstage_task_assets(task, repo=repo, clone=clone, snapshot=snapshot)","handlingStrategy":"validation","validationCode":"from .task_assets import _sandbox_dependency_declarations, TaskAssetCache\n\ndef stage_safely(task, *, repo, clone, cache_dir, resolved_sha):\n    if _sandbox_dependency_declarations(task):\n        with TaskAssetCache(cache_dir) as cache:\n            snapshot = cache.prepare(task, repo=repo, resolved_sha=resolved_sha)\n    else:\n        snapshot = None   # sandbox_copy-only: ephemeral snapshot path is fine\n    return stage_task_assets(task, repo=repo, clone=clone, snapshot=snapshot)\n\n# Always build a caller-owned snapshot when the task declares sandbox_dependencies.","typeGuard":"def needs_caller_snapshot(task) -> bool:\n    return bool(_sandbox_dependency_declarations(task))","tryCatchPattern":"from .proposer_sandbox import SandboxError\n\ntry:\n    stage_task_assets(task, repo=repo, clone=clone)   # no snapshot\nexcept SandboxError as exc:\n    if 'require a caller-owned immutable task asset snapshot' in str(exc):\n        with TaskAssetCache(cache_dir) as cache:\n            snapshot = cache.prepare(task, repo=repo, resolved_sha=sha)\n        stage_task_assets(task, repo=repo, clone=clone, snapshot=snapshot)\n    else:\n        raise","preventionTips":["Always plumb a caller-owned snapshot through when the task declares sandbox_dependencies.","Use capture_task_dependency_binding + cache.prepare in the benchmark runner; do not call stage_task_assets bare for dependency tasks.","Drop sandbox_dependencies from the task if you only need sandbox_copy and want the ephemeral path."],"tags":["api-usage","dependencies","validation","sandbox"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}