abhigyanpatwari/GitNexus · error · SandboxError

{label} must be a real directory: {lexical}: {exc}

Error message

{label} must be a real directory: {lexical}: {exc}

What it means

Raised by _real_directory when lstat() on the lexical (expanded, absolute) path raises OSError. _real_directory exists to reject any symlink hop and must first stat the path exactly as given; an OSError here (ENOENT, EACCES, ELOOP) means the directory is not even inspectable, so the label-specific message includes the original exception text.

Source

Thrown at eval/workflow_bench/proposer_sandbox.py:549

        "--",
        *command,
    ]


def require_claude_sandbox_helpers() -> None:
    """Fail before paid work when Claude's mandatory inner sandbox cannot run."""

    _resolve_executable(None, "socat")


def _real_directory(path: Path, *, label: str) -> Path:
    """Return an absolute directory path without accepting any symlink hop."""

    lexical = path.expanduser().absolute()
    try:
        mode = lexical.lstat().st_mode
    except OSError as exc:
        raise SandboxError(f"{label} must be a real directory: {lexical}: {exc}") from exc
    if stat.S_ISLNK(mode) or not stat.S_ISDIR(mode):
        raise SandboxError(f"{label} must be a real directory: {lexical}")
    try:
        resolved = lexical.resolve(strict=True)
    except OSError as exc:
        raise SandboxError(f"{label} must be a real directory: {lexical}: {exc}") from exc
    if resolved != lexical:
        raise SandboxError(f"{label} must not traverse symlinks: {lexical}")
    return lexical


def _safe_repo_source(repo: Path, relative: str, *, label: str) -> tuple[Path, Path]:
    candidate = PurePosixPath(relative)
    if candidate.is_absolute() or ".." in candidate.parts or not candidate.parts:
        raise SandboxError(f"{label} must be a repository-relative path: {relative!r}")
    lexical = repo / Path(*candidate.parts)
    resolved = lexical.resolve()
    try:

View on GitHub (pinned to d540b00184)

Solutions

  1. Create the directory before validation: path.mkdir(parents=True, exist_ok=True).
  2. Confirm the path is correct (typo, wrong env var) with ls -ld from the same user.
  3. Fix permissions on the parent components so lstat succeeds.
  4. Resolve env vars upstream and fail with a clear config error if unset.

Example fix

// before
real = _real_directory(Path(mount_src), label='mount source')  # missing dir
// after
mount_src = Path(mount_src)
mount_src.mkdir(parents=True, exist_ok=True)
real = _real_directory(mount_src, label='mount source')
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

def ensure_real_dir(p: Path) -> Path:
    p = p.expanduser().absolute()
    try:
        p.lstat()
    except OSError as exc:
        raise ValueError(f'cannot stat {p}: {exc}') from exc
    return p

# call ensure_real_dir before _real_directory to get a clearer error

Type guard

null

Try / catch

try:
    real = _real_directory(path, label=label)
except SandboxError as exc:
    if label in str(exc) and 'must be a real directory' in str(exc) and ': ' in str(exc):
        path.mkdir(parents=True, exist_ok=True)
        real = _real_directory(path, label=label)
    raise

Prevention

When it happens

Trigger: Calling code that funnels a path through _real_directory (mount sources, repo roots, runtime dirs) with a path whose lstat fails: nonexistent path, missing parent, permission denied on a component, too many symlink levels (ELOOP).

Common situations: Config points a directory env var at a path that was never created; a mount source directory is created lazily but the preflight ran before creation; CI runs as a user without read on the parent; a path constructed from an unset env var produced a literal like '/'; a symlink loop in the path prefix.

Related errors


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