abhigyanpatwari/GitNexus · error · ValueError

oracle root must be a real non-symlink directory: {lexical}

Error message

oracle root must be a real non-symlink directory: {lexical}

What it means

Raised by _real_oracle_root when the oracle root exists but is a symlink, is not a directory, or its resolved path differs from its lexical path (i.e. it is reachable through a symlink). The harness requires a real, non-symlink directory to prevent path-traversal and TOCTOU tricks against the oracle store.

Source

Thrown at eval/workflow_bench/oracle_assets.py:124

        source = _bounded_relative_path(declaration.get("source"), label=f"task {task_id} oracle source")
        target = _bounded_relative_path(declaration.get("target"), label=f"task {task_id} oracle target")
        if source.as_posix() in sources:
            raise ValueError(f"task {task_id} oracle source is duplicated: {source}")
        if target.as_posix() in targets:
            raise ValueError(f"task {task_id} oracle target is duplicated: {target}")
        sources.add(source.as_posix())
        targets.add(target.as_posix())


def _real_oracle_root(root: Path) -> Path:
    lexical = root.expanduser().absolute()
    try:
        metadata = lexical.lstat()
        resolved = lexical.resolve(strict=True)
    except OSError as exc:
        raise ValueError(f"oracle root is unavailable: {lexical}") from exc
    if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode) or resolved != lexical:
        raise ValueError(f"oracle root must be a real non-symlink directory: {lexical}")
    return lexical


def _read_oracle_file(root: Path, relative: PurePosixPath) -> bytes:
    current = root
    for part in relative.parts[:-1]:
        current /= part
        try:
            metadata = current.lstat()
        except OSError as exc:
            raise ValueError(f"oracle parent is unreadable: {relative}") from exc
        if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):
            raise ValueError(f"oracle parents must be real directories: {relative}")

    path = root.joinpath(*relative.parts)
    try:
        before = path.lstat()
        if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode):

View on GitHub (pinned to d540b00184)

Solutions

  1. Point the oracle root at a real directory, not a symlink.
  2. Replace any symlink in the path chain with the actual directory it resolves to.
  3. Ensure no parent directory of the oracle root is a symlink; use readlink -f to find the real location and use that.
  4. Confirm the target is a directory (stat shows S_IFDIR).
Defensive patterns

Strategy: validation

Validate before calling

import stat
from pathlib import Path

def assert_real_dir(path: Path) -> None:
    lex = path.expanduser().absolute()
    md = lex.lstat()
    if stat.S_ISLNK(md.st_mode) or not stat.S_ISDIR(md.st_mode) or lex.resolve(strict=True) != lex:
        raise ValueError(f"{lex} must be a real non-symlink directory")

Type guard

def is_real_non_symlink_dir(path) -> bool:
    import stat
    p = Path(path).expanduser().absolute()
    try:
        md = p.lstat()
    except OSError:
        return False
    return not stat.S_ISLNK(md.st_mode) and stat.S_ISDIR(md.st_mode) and p.resolve(strict=True) == p

Try / catch

try:
    snap = capture_task_oracle(task, root=root)
except ValueError as exc:
    if "must be a real non-symlink directory" in str(exc):
        root = root.resolve(strict=True)
        snap = capture_task_oracle(task, root=root)
    else:
        raise

Prevention

When it happens

Trigger: oracle root is a symbolic link; it is a regular file rather than a directory; an intermediate component of the path is a symlink causing resolved != lexical.

Common situations: Someone symlinked the oracles directory for convenience; the path points at a file; the working directory is itself under a symlink (e.g. /tmp is a symlink on some macOS setups) making resolve() diverge from the lexical absolute path.

Related errors


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