abhigyanpatwari/GitNexus · error · SandboxError
sandbox_copy must not traverse a symlink: {child_relative}
Error message
sandbox_copy must not traverse a symlink: {child_relative} What it means
In _SnapshotBuilder.copy_descriptor, a child entry is a symlink but the builder was constructed with allow_symlinks=False (the default for sandbox_copy; only sandbox_dependencies set it True). Symlink traversal in copied assets is rejected because a symlink could escape the snapshot boundary.
Source
Thrown at eval/workflow_bench/task_assets.py:400
self.total_bytes = 0
self.budget = budget if budget is not None else _SnapshotBudget()
self.allow_symlinks = allow_symlinks
self.preserve_modes = preserve_modes
def copy_descriptor(self, descriptor: int, relative: PurePosixPath) -> None:
before = os.fstat(descriptor)
if stat.S_ISDIR(before.st_mode):
self._record_directory(relative)
try:
names = sorted(os.listdir(descriptor))
except OSError as exc:
raise SandboxError(f"sandbox_copy directory is unreadable: {relative}: {exc}") from exc
for name in names:
child_relative = relative / name
child_metadata = os.stat(name, dir_fd=descriptor, follow_symlinks=False)
if stat.S_ISLNK(child_metadata.st_mode):
if not self.allow_symlinks:
raise SandboxError(f"sandbox_copy must not traverse a symlink: {child_relative}")
self._copy_symlink(descriptor, name, child_relative, child_metadata)
continue
child = _open_child(descriptor, name, child_relative)
try:
self.copy_descriptor(child, child_relative)
finally:
os.close(child)
after = os.fstat(descriptor)
if _mutation_identity(before) != _mutation_identity(after):
raise SandboxError(f"sandbox_copy directory changed while snapshotting: {relative}")
return
if not stat.S_ISREG(before.st_mode):
raise SandboxError(f"sandbox_copy accepts only regular files and directories: {relative}")
self._copy_file(descriptor, relative, before)
def _record_directory(self, relative: PurePosixPath) -> None:
self._ensure_parents(relative.parent)
self._record(AssetManifestEntry(path=relative, kind="directory"))View on GitHub (pinned to d540b00184)
Solutions
- Remove or resolve the symlink in the source tree before declaring it in sandbox_copy.
- If the symlinked content is a real dependency, declare it under sandbox_dependencies (which permits bounded relative symlinks).
- Materialize the symlink target as a real file or directory in the repo.
Example fix
# before - 'tools/current' is a symlink to tools/v1.2 sandbox_copy: - "tools/current" # after - resolve the link, or declare as a dependency sandbox_copy: - "tools/v1.2"
Defensive patterns
Strategy: validation
Validate before calling
import os
def assert_no_symlinks_in_sandbox_copy(repo, paths):
for raw in paths:
root = os.path.join(repo, raw)
for dirpath, dirnames, filenames in os.walk(root, followlinks=False):
for name in dirnames + filenames:
p = os.path.join(dirpath, name)
if os.path.islink(p):
raise RuntimeError(f"sandbox_copy contains a symlink: {p}") Prevention
- Resolve or remove symlinks inside any sandbox_copy tree before declaring it.
- Declare symlink-bearing dependencies (e.g. node_modules/.bin) under sandbox_dependencies, which allows bounded relative symlinks.
- Pre-scan sandbox_copy roots for symlinks to fail fast with a clearer message.
When it happens
Trigger: A sandbox_copy path contains a symlink - e.g. a symlinked file or directory inside the copied tree such as a node_modules/.bin-style link.
Common situations: A repo contains convenience symlinks; node_modules/.bin-style symlinks captured via sandbox_copy instead of as a dependency; a symlink loop.
Related errors
- {label} must be a real non-symlink directory: {path}
- {label} contains an unsafe path component: {relative}
- {label} must be a regular non-symlink file: {path}
- {label} changed while opening: {path}
- evidence source must be a regular non-symlink file: {path}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/53551e802ccdcc20.
Report an issue: GitHub.