abhigyanpatwari/GitNexus · error · ValueError
{label} must be a regular non-symlink file: {path}
Error message
{label} must be a regular non-symlink file: {path} What it means
Thrown by _bounded_regular_bytes() in eval/workflow_bench/evolution.py when lstat() succeeds but the entry is a symlink (S_ISLNK) or not a regular file (not S_ISREG): the evidence reader refuses to follow symlinks and refuses special files (FIFO/socket/device) because they could be used to escape or to deliver unbounded bytes.
Source
Thrown at eval/workflow_bench/evolution.py:94
_require_real_directory(root, label=label)
current = root
for part in relative.parts:
if part in {"", ".", ".."}:
raise ValueError(f"{label} contains an unsafe path component: {relative}")
current /= part
_require_real_directory(current, label=label)
def _bounded_regular_bytes(path: Path, *, limit: int, label: str) -> bytes:
"""Read one bounded regular file without following its leaf link."""
try:
before = path.lstat()
except OSError as exc:
raise ValueError(f"{label} is unreadable: {path}: {exc}") from exc
if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode):
raise ValueError(f"{label} must be a regular non-symlink file: {path}")
if before.st_size > limit:
raise ValueError(f"{label} exceeds the bounded evidence limit")
descriptor = os.open(path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0))
try:
opened = os.fstat(descriptor)
if not stat.S_ISREG(opened.st_mode) or opened.st_dev != before.st_dev or opened.st_ino != before.st_ino:
raise ValueError(f"{label} changed while opening: {path}")
chunks: list[bytes] = []
remaining = limit + 1
while remaining > 0:
chunk = os.read(descriptor, min(64 * 1024, remaining))
if not chunk:
break
chunks.append(chunk)
remaining -= len(chunk)
content = b"".join(chunks)
if len(content) > limit:View on GitHub (pinned to d540b00184)
Solutions
- Replace symlinks with real files: `cp -rL` to dereference at staging time.
- Re-extract the candidate tarball with symlink dereference or filter non-regular entries.
- Audit the candidate generator to write regular files only.
- Treat special files as a security signal if provenance is untrusted.
Example fix
# before
ln -s /etc/passwd /tmp/ov/leaked.yaml
_bounded_regular_bytes(Path('/tmp/ov/leaked.yaml'), limit=L, label='candidate overlay file')
# ValueError: candidate overlay file must be a regular non-symlink file
# after
cp /etc/passwd /tmp/ov/leaked.yaml # or, better, drop the entry entirely Defensive patterns
Strategy: validation
Validate before calling
import os, stat
def ensure_regular_file(p: Path) -> None:
st = p.lstat()
if stat.S_ISLNK(st.st_mode) or not stat.S_ISREG(st.st_mode):
raise SystemExit(f'evidence must be a regular file, not symlink/special: {p}')
# call before _bounded_regular_bytes Type guard
def is_not_regular_file_error(exc: ValueError) -> bool:
return 'must be a regular non-symlink file' in str(exc) Try / catch
try:
_bounded_regular_bytes(p, limit=L, label='candidate overlay file')
except ValueError as e:
if is_not_regular_file_error(e):
# dereference with cp -L or drop the entry, then retry
raise
raise Prevention
- Dereference symlinks at staging time (cp -rL).
- Filter non-regular entries when extracting candidate tarballs.
- Audit candidate generators to emit regular files only.
When it happens
Trigger: A candidate overlay file that is a symlink, a FIFO, a socket, or a device node. The reader opens with O_NOFOLLOW and rejects anything not a plain regular file before reading.
Common situations: Tarball that preserved symlinks; a user symlinked a shared skill file into many overlays; a misbehaving candidate generator that wrote a FIFO; an absolute symlink pointing outside the sandbox.
Related errors
- {label} must be a real non-symlink directory: {path}
- {label} is unavailable: {path}: {exc}
- {label} contains an unsafe path component: {relative}
- {label} is unreadable: {path}: {exc}
- {label} changed while opening: {path}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/76dc620d9991ab8a.
Report an issue: GitHub.