abhigyanpatwari/GitNexus · error · SandboxError

read-only sandbox path must be real and non-symlink: {raw_pa

Error message

read-only sandbox path must be real and non-symlink: {raw_path}

What it means

Thrown by `command_prefix_for` after a `read_only_paths` entry resolved successfully but failed the 'real and non-symlink' check: the resolved path differs from the lexical path (symlink traversal), `lstat` reports a symlink, or the entry is neither a directory nor a regular file. Bubblewrap bind mounts must point at real on-disk inodes.

Source

Thrown at eval/workflow_bench/proposer_sandbox.py:135

        for harness-owned, post-session evidence such as hidden oracles.
        """

        additional: list[ReadOnlyMount] = []
        clone = _real_directory(self.clone, label="sandbox clone")
        for raw_path in read_only_paths:
            lexical = raw_path.expanduser().absolute()
            try:
                relative = lexical.relative_to(clone)
                metadata = lexical.lstat()
                resolved = lexical.resolve(strict=True)
            except (OSError, ValueError) as exc:
                raise SandboxError(f"read-only sandbox path is unavailable: {raw_path}") from exc
            if (
                resolved != lexical
                or stat.S_ISLNK(metadata.st_mode)
                or not (stat.S_ISDIR(metadata.st_mode) or stat.S_ISREG(metadata.st_mode))
            ):
                raise SandboxError(f"read-only sandbox path must be real and non-symlink: {raw_path}")
            additional.append(
                ReadOnlyMount(
                    source=lexical,
                    target=f"{SANDBOX_WORKSPACE}/{PurePosixPath(relative.as_posix())}",
                )
            )

        for mount in extra_read_only_mounts:
            source = mount.source.expanduser().absolute()
            try:
                metadata = source.lstat()
                resolved = source.resolve(strict=True)
            except OSError as exc:
                raise SandboxError(f"extra read-only mount is unavailable: {source}") from exc
            if (
                resolved != source
                or stat.S_ISLNK(metadata.st_mode)
                or not (stat.S_ISDIR(metadata.st_mode) or stat.S_ISREG(metadata.st_mode))

View on GitHub (pinned to d540b00184)

Solutions

  1. Pass the real target of the symlink: `p = p.resolve(strict=True)` and use the resolved path.
  2. Remove or replace the symlink with the real file/dir inside the clone before freezing.
  3. Filter out non-regular, non-directory entries: `assert p.is_dir() or p.is_file()` and `not p.is_symlink()`.
  4. If you genuinely need a symlink frozen, copy its target into the clone as a regular file.

Example fix

# before: p is a symlink
prefix = session.command_prefix_for(read_only_paths=[p])  # -> SandboxError
# after: dereference first
real = p.expanduser().resolve(strict=True)
assert real.is_dir() or real.is_file()
prefix = session.command_prefix_for(read_only_paths=[real])
Defensive patterns

Strategy: validation

Validate before calling

import stat
from pathlib import Path

def is_real_nonsymlink_under_clone(clone: Path, p: Path) -> bool:
    try:
        lexical = p.expanduser().absolute()
        lexical.relative_to(clone)
        meta = lexical.lstat()
        resolved = lexical.resolve(strict=True)
    except (OSError, ValueError):
        return False
    return (
        resolved == lexical
        and not stat.S_ISLNK(meta.st_mode)
        and (stat.S_ISDIR(meta.st_mode) or stat.S_ISREG(meta.st_mode))
    )

Type guard

import stat
from pathlib import Path

def is_freezable_real_path(clone: Path, p: Path) -> bool:
    """Narrow to paths that command_prefix_for will accept."""
    if not is_real_nonsymlink_under_clone(clone, p):
        return False
    return True

Try / catch

try:
    prefix = session.command_prefix_for(read_only_paths=paths)
except SandboxError as exc:
    if "must be real and non-symlink" in str(exc):
        # dereference symlinks / drop sockets, then retry
        paths = [p.expanduser().resolve(strict=True) for p in paths
                 if is_real_nonsymlink_under_clone(session.clone, p.expanduser().resolve(strict=True))]
        prefix = session.command_prefix_for(read_only_paths=paths)
    raise

Prevention

When it happens

Trigger: Passing a symlink as a `read_only_paths` entry; a path whose `resolve(strict=True)` crosses a symlink (`resolved != lexical`); a path that is a socket/FIFO/device file (not dir/regular); a path that is itself a symlink to a directory.

Common situations: The clone contains a symlink (e.g. `node_modules` linked elsewhere) that the caller tried to freeze; `/tmp` paths that are symlinked; a path that resolves through `/usr/local/...` symlink chains; a fifo/socket the harness mistook for a file.

Related errors


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