abhigyanpatwari/GitNexus · error · SandboxError

extra read-only mount must be real and non-symlink: {source}

Error message

extra read-only mount must be real and non-symlink: {source}

What it means

Thrown by `command_prefix_for` after an `extra_read_only_mounts` source was probed successfully but failed the 'real and non-symlink' check: resolved != lexical, `lstat` reports a symlink, or the entry is neither directory nor regular file. The same constraint as error 416, applied to extra mount sources.

Source

Thrown at eval/workflow_bench/proposer_sandbox.py:155

                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))
            ):
                raise SandboxError(f"extra read-only mount must be real and non-symlink: {source}")
            target = PurePosixPath(mount.target)
            if not target.is_absolute() or ".." in target.parts:
                raise SandboxError(f"extra read-only mount target must be absolute: {mount.target}")
            additional.append(ReadOnlyMount(source=source, target=target.as_posix()))

        return _sandbox_command_prefix(
            bwrap=self.bwrap_bin,
            clone=clone,
            home=self.home,
            temp=self.temp,
            claude_bin=self.claude_host_bin,
            mounts=(*self.read_only_mounts, *additional),
            read_only_workspace=read_only_workspace,
            unshare_network=unshare_network,
        )


_TOKEN_PATTERNS = (

View on GitHub (pinned to d540b00184)

Solutions

  1. Dereference the source: `source = ReadOnlyMount(source=p.resolve(strict=True), target=t)`.
  2. Write evidence as a regular file (not a symlink) and freeze that path.
  3. Filter: `assert (p.is_dir() or p.is_file()) and not p.is_symlink()` before constructing the mount.
  4. If the source is a symlinked directory by design, replace it with a real directory.

Example fix

# before: oracle is a symlink
mount = ReadOnlyMount(source=Path('/evidence/oracle'), target='/workspace/oracle.json')
# /evidence/oracle -> /shared/oracle  (symlink)
prefix = session.command_prefix_for(extra_read_only_mounts=[mount])  # -> SandboxError
# after: bind the resolved real path
mount = ReadOnlyMount(
    source=Path('/evidence/oracle').resolve(strict=True),
    target='/workspace/oracle.json',
)
prefix = session.command_prefix_for(extra_read_only_mounts=[mount])
Defensive patterns

Strategy: validation

Validate before calling

import stat
from pathlib import Path

def is_real_nonsymlink_path(p: Path) -> bool:
    try:
        source = p.expanduser().absolute()
        meta = source.lstat()
        resolved = source.resolve(strict=True)
    except OSError:
        return False
    return (
        resolved == source
        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_extra_mount_source(p: Path) -> bool:
    return is_real_nonsymlink_path(p)

Try / catch

try:
    prefix = session.command_prefix_for(extra_read_only_mounts=mounts)
except SandboxError as exc:
    if "extra read-only mount must be real and non-symlink" in str(exc):
        # dereference symlinks, drop non-regular entries
        mounts = [
            ReadOnlyMount(source=m.source.resolve(strict=True), target=m.target)
            for m in mounts
            if is_real_nonsymlink_path(m.source.resolve(strict=True))
        ]
        prefix = session.command_prefix_for(extra_read_only_mounts=mounts)
    raise

Prevention

When it happens

Trigger: An `extra_read_only_mounts` source that is a symlink, resolves through a symlink chain, or is a socket/FIFO/device rather than a dir or regular file. Bubblewrap needs a real inode to bind.

Common situations: Evidence file replaced with a symlink to a shared store; `/var/lib/.../oracle` is a symlink; an oracle written as a fifo from a streaming producer; a path that crosses `/proc` or `/sys` symlinks.

Related errors


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