abhigyanpatwari/GitNexus · error · SandboxError
task asset snapshot contains an unexpected symlink: {path}
Error message
task asset snapshot contains an unexpected symlink: {path} What it means
Raised by _freeze_snapshot during the files walk when a symlink is found whose first relative path component is not 'dependencies'. Symlinks are permitted only inside the dependencies/ subtree of the snapshot; anywhere else they are a containment violation because they could escape the snapshot root or alias mutable state.
Source
Thrown at eval/workflow_bench/task_assets.py:993
"dependency_content_digest": dependency_content_digest,
"dependency_manifest_digest": dependency_manifest_digest,
"manifest_digest": manifest_digest,
"repo_identity": str(repo_identity),
"resolved_sha": resolved_sha,
"schema_version": 2,
}
return hashlib.sha256(json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
def _freeze_snapshot(root: Path) -> None:
for current, directories, files in os.walk(root, topdown=False, followlinks=False):
for name in files:
path = Path(current) / name
mode = path.lstat().st_mode
relative = path.relative_to(root)
if stat.S_ISLNK(mode):
if not relative.parts or relative.parts[0] != "dependencies":
raise SandboxError(f"task asset snapshot contains an unexpected symlink: {path}")
continue
if not stat.S_ISREG(mode):
raise SandboxError(f"task asset snapshot contains a special file: {path}")
path.chmod(0o400 | (0o100 if stat.S_IMODE(mode) & 0o111 else 0))
for name in directories:
path = Path(current) / name
mode = path.lstat().st_mode
relative = path.relative_to(root)
if stat.S_ISLNK(mode):
if not relative.parts or relative.parts[0] != "dependencies":
raise SandboxError(f"task asset snapshot contains an unexpected symlink: {path}")
continue
if not stat.S_ISDIR(mode):
raise SandboxError(f"task asset snapshot contains a special directory: {path}")
path.chmod(0o500)
Path(current).chmod(0o500)
View on GitHub (pinned to d540b00184)
Solutions
- Move the dependency under the dependencies/ subtree and declare it via sandbox_dependencies, which is the only symlink-allowed location.
- Resolve or remove the symlink from the captured tree (replace with the real file or a copy) before capture.
- Narrow the sandbox_copy declaration so it does not include the offending symlink.
Example fix
# before: symlink lives in a sandbox_copy'd directory
repo/include/version.h -> ../shared/version.h # captured -> freeze fails
# after: declare it as a dependency (symlinks allowed under dependencies/)
sandbox_dependencies = [{'source': 'shared/version.h', 'target': 'include/version.h'}] Defensive patterns
Strategy: validation
Validate before calling
import os, stat
from pathlib import Path
def assert_no_external_symlinks(root: Path) -> None:
for current, dirs, files in os.walk(root, followlinks=False):
rel = Path(current).relative_to(root)
for name in files + dirs:
p = Path(current) / name
if stat.S_ISLNK(p.lstat().st_mode):
first = rel.parts[0] if rel.parts else (name,)
if not rel.parts or rel.parts[0] != 'dependencies':
raise ValueError(f'external symlink would be rejected by freeze: {p}')
# Run before cache.prepare; move offending symlinks under dependencies/ or remove them. Prevention
- Keep all symlinked content under the dependencies/ subtree and declare it via sandbox_dependencies.
- Resolve or remove convenience symlinks (version managers, vendored aliases) from sandbox_copy'd trees.
- Pre-scan captured roots with the validator above so freeze never surprises you.
When it happens
Trigger: A sandbox_copy declaration captured a tree that contains a symlink (file or symlinked regular file) outside the dependencies/ directory, and _freeze_snapshot walked it. E.g. a repo symlink like include/foo -> ../shared/foo, or a vendored library with convenience symlinks.
Common situations: Capturing a repo with version-manager (asdf/nvm/direnv) symlinks, broken convenience symlinks, or symlinked vendored dependencies not declared via sandbox_dependencies; switching sandbox_copy to a broader root that now includes previously-excluded symlinks.
Related errors
- evidence source must be a regular non-symlink file: {path}
- results directory must not traverse symlinks: {root}
- results artifact parent must be a real directory: {current}
- transcript artifact parent must be owner-only: {current}
- evidence must be a regular non-symlink file: {value}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/530bec536307b13e.
Report an issue: GitHub.