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

  1. Remove or resolve the symlink in the source tree before declaring it in sandbox_copy.
  2. If the symlinked content is a real dependency, declare it under sandbox_dependencies (which permits bounded relative symlinks).
  3. 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

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


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