abhigyanpatwari/GitNexus · error · SandboxError

evidence source must be a regular non-symlink file: {path}

Error message

evidence source must be a regular non-symlink file: {path}

What it means

_bounded_regular_text reads an evidence file (patch, redacted excerpt) into the proposer prompt. It lstat's the path and refuses symlinks (path.is_symlink()) and any non-regular file (not stat.S_ISREG). The guard prevents a crafted results tree from redirecting the read through a symlink to an arbitrary host file before it crosses the sandbox trust boundary.

Source

Thrown at eval/workflow_bench/evolve.py:281

  gitnexus/test/unit/skills-steering.test.ts before rewording any command.
- Never weaken the skills' hard gates: impact-before-edit,
  detect_changes-before-commit, foreground verification.
- Keep the edit small — a rule added, sharpened, or deleted; a budget
  adjusted; a phase reordered. A sprawling rewrite loses in human review even
  if it wins the gate.

Finally write {proposal_path}: the failure pattern (cite task/arm/session
ids), the single change you made, the metric you expect to move and why, and
the risks. That file is the reviewer-facing case for the candidate."""


# ─── Proposer session ────────────────────────────────────────────────────────


def _bounded_regular_text(path: Path, limit: int = MAX_EVIDENCE_FILE_BYTES) -> str:
    mode = path.lstat().st_mode
    if path.is_symlink() or not stat.S_ISREG(mode):
        raise SandboxError(f"evidence source must be a regular non-symlink file: {path}")
    with path.open("rb") as handle:
        if path.stat().st_size > limit:
            handle.seek(-limit, os.SEEK_END)
        return handle.read(limit).decode(errors="replace")


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

View on GitHub (pinned to d540b00184)

Solutions

  1. Locate the offending entry: `find <evidence_dir> -type l`.
  2. Materialize it: `cp -L --remove-destination <link> <link>`.
  3. Regenerate the evidence bundle so it contains only regular files.

Example fix

# before
ln -s /shared/excerpts/run-42.patch evidence/run-42.patch
_bounded_regular_text(Path("evidence/run-42.patch"))

# after
cp /shared/excerpts/run-42.patch evidence/run-42.patch
_bounded_regular_text(Path("evidence/run-42.patch"))
Defensive patterns

Strategy: validation

Validate before calling

import stat
from pathlib import Path

def evidence_file_is_regular(path: Path) -> bool:
    mode = path.lstat().st_mode
    return not path.is_symlink() and stat.S_ISREG(mode)

# before reading evidence
for f in evidence_dir.rglob("*"):
    if f.is_file() and not evidence_file_is_regular(f):
        raise ValueError(f"non-regular evidence file: {f}")

Type guard

import stat
from pathlib import Path

def is_safe_evidence_file(path: Path) -> bool:
    return path.exists() and not path.is_symlink() and stat.S_ISREG(path.lstat().st_mode)

Prevention

When it happens

Trigger: An evidence file under the staged evidence dir is a symlink (e.g. `ln -s /etc/passwd patch.diff`), a broken symlink, or a special file; a packaging step symlinked shared excerpts.

Common situations: Shared redacted excerpts reused across runs via symlinks; a proposer or upstream tool that symlinks outputs into the evidence dir; broken symlinks after the evidence root was moved.

Related errors


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