abhigyanpatwari/GitNexus · critical · SandboxError

sandbox_copy must not traverse a symlink: {relative}

Error message

sandbox_copy must not traverse a symlink: {relative}

What it means

Raised by _open_child when the pre-open os.stat shows S_ISLNK on any path component of a declared sandbox_copy path. The snapshot walker never crosses symlinks because a symlink could redirect capture outside the repo root, breaking the containment contract that SandboxError guards.

Source

Thrown at eval/workflow_bench/task_assets.py:630

        return current
    except BaseException:
        os.close(current)
        raise


def _open_child(
    parent_descriptor: int,
    name: str,
    relative: PurePosixPath,
    *,
    require_directory: bool = False,
) -> int:
    try:
        metadata = os.stat(name, dir_fd=parent_descriptor, follow_symlinks=False)
    except OSError as exc:
        raise SandboxError(f"sandbox_copy path is unavailable: {relative}: {exc}") from exc
    if stat.S_ISLNK(metadata.st_mode):
        raise SandboxError(f"sandbox_copy must not traverse a symlink: {relative}")
    if require_directory and not stat.S_ISDIR(metadata.st_mode):
        raise SandboxError(f"sandbox_copy parent must be a directory: {relative}")
    if not (stat.S_ISDIR(metadata.st_mode) or stat.S_ISREG(metadata.st_mode)):
        raise SandboxError(f"sandbox_copy accepts only regular files and directories: {relative}")
    flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0)
    if stat.S_ISDIR(metadata.st_mode):
        flags |= os.O_DIRECTORY
    else:
        flags |= getattr(os, "O_NONBLOCK", 0)
    try:
        descriptor = os.open(name, flags, dir_fd=parent_descriptor)
    except OSError as exc:
        raise SandboxError(f"sandbox_copy path changed or is unreadable: {relative}: {exc}") from exc
    opened = os.fstat(descriptor)
    if not (stat.S_ISDIR(opened.st_mode) or stat.S_ISREG(opened.st_mode)):
        os.close(descriptor)
        raise SandboxError(f"sandbox_copy accepts only regular files and directories: {relative}")
    if (

View on GitHub (pinned to d540b00184)

Solutions

  1. Resolve the symlink target and declare the real path it points to
  2. Remove the symlink from the worktree at the captured SHA, or exclude that subtree from the declaration
  3. If the linked content is genuinely needed, materialize it as a regular file/dir before capture

Example fix

// before (declaration points at a symlink)
//   sandbox_copy:
//     - src/build        # 'build' is a symlink -> 'dist/build'
// after
//   sandbox_copy:
//     - src/dist/build   # the real directory
Defensive patterns

Strategy: validation

Validate before calling

import os, stat
from pathlib import Path

def find_symlinks_under(root: Path, declared: list[str]) -> list[str]:
    hits = []
    for decl in declared:
        base = root / decl
        if base.is_symlink():
            hits.append(str(decl)); continue
        cur = base.parent
        parts = list(Path(decl).parts)
        walker = root
        for part in parts:
            try:
                if os.path.islink(walker / part):
                    hits.append(f"{decl} (symlink at {walker/part})"); break
            except OSError:
                break
            walker = walker / part
    return hits
# abort prepare if hits is non-empty

Try / catch

from eval.workflow_bench.proposer_sandbox import SandboxError

try:
    snapshot = cache.prepare(task, repo=repo, resolved_sha=sha)
except SandboxError as exc:
    if "must not traverse a symlink" in str(exc):
        raise SystemExit(f"declaration crosses a symlink; resolve the real path: {exc}") from exc
    raise

Prevention

When it happens

Trigger: A sandbox_copy declaration (or one of its parent directories) is a symlink in the worktree at the resolved SHA; e.g. a top-level convenience link like build -> dist/build, or an installed package alias.

Common situations: Monorepos with cross-package symlinks; packages that create relative symlinks during install; a task pin captured after npm install wrote symlinks into the copied tree.

Related errors


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