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
- Pass the real target of the symlink: `p = p.resolve(strict=True)` and use the resolved path.
- Remove or replace the symlink with the real file/dir inside the clone before freezing.
- Filter out non-regular, non-directory entries: `assert p.is_dir() or p.is_file()` and `not p.is_symlink()`.
- 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
- Dereference symlinks before passing them: `p = p.resolve(strict=True)`.
- Filter out sockets/fifos/devices — only dirs and regular files are freezable.
- Avoid symlinks inside the clone for paths you intend to freeze.
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
- extra read-only mount must be real and non-symlink: {source}
- read-only sandbox path is unavailable: {raw_path}
- extra read-only mount is unavailable: {source}
- extra read-only mount target must be absolute: {mount.target
- {label} must be a real non-symlink directory: {path}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/0370358f26e2dfbf.
Report an issue: GitHub.