abhigyanpatwari/GitNexus · error · SandboxError
evidence must be a regular non-symlink file: {value}
Error message
evidence must be a regular non-symlink file: {value} What it means
Raised by _evidence_bytes when a Path evidence value is a symlink or not a regular file (stat.S_ISREG false). The contract forbids symlinks and special files because they could redirect the redaction pass at the real target or yield non-file bytes (FIFO/socket/device), defeating the byte cap and redaction guarantees.
Source
Thrown at eval/workflow_bench/proposer_sandbox.py:198
def redact_text(text: str, secrets: Sequence[str] = ()) -> str:
for secret in secrets:
if secret:
text = text.replace(secret, "[REDACTED]")
text = _TOKEN_PATTERNS[0].sub("[REDACTED]", text)
text = _TOKEN_PATTERNS[1].sub("[REDACTED]", text)
text = _TOKEN_PATTERNS[2].sub(r"\1[REDACTED]", text)
return _TOKEN_PATTERNS[3].sub(r"\1[REDACTED]@", text)
def _evidence_bytes(value: Any, secrets: Sequence[str]) -> bytes:
if isinstance(value, Path):
try:
mode = value.lstat().st_mode
except OSError as exc:
raise SandboxError(f"evidence path is unreadable: {value}: {exc}") from exc
if value.is_symlink() or not stat.S_ISREG(mode):
raise SandboxError(f"evidence must be a regular non-symlink file: {value}")
if value.stat().st_size > MAX_EVIDENCE_FILE_BYTES:
raise SandboxError(f"evidence exceeds the per-file limit: {value}")
raw = value.read_bytes()
return redact_text(raw.decode(errors="replace"), secrets).encode()
if isinstance(value, bytes):
raw = value
elif isinstance(value, str):
raw = value.encode()
else:
raw = (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode()
return redact_text(raw.decode(errors="replace"), secrets).encode()
def stage_evidence_bundle(
destination: Path,
entries: Mapping[str, Any],
*,
secrets: Sequence[str] = (),View on GitHub (pinned to d540b00184)
Solutions
- Replace the symlink with the real file: read the target and pass its bytes, or copy the target into a plain file with shutil.copyfile then stage that.
- If you meant to stage a directory, enumerate its regular files and stage each as its own simple-name entry.
- Reject special files upstream: assert value.is_file() and not value.is_symlink() before building the entries mapping.
- For symlinks you control, dereference explicitly with value.resolve(strict=True) and stage the resolved regular file.
Example fix
// before
entries = {"out": Path("/workspace/out")} # /workspace/out -> /secret/log
// after
target = Path("/workspace/out").resolve(strict=True)
assert target.is_file() and not target.is_symlink()
entries = {"out": target} Defensive patterns
Strategy: validation
Validate before calling
import stat
from pathlib import Path
def is_regular_nonsymlink(p: Path) -> bool:
try:
mode = p.lstat().st_mode
except OSError:
return False
return stat.S_ISREG(mode) and not stat.S_ISLNK(mode)
assert all(is_regular_nonsymlink(v) for v in entries.values() if isinstance(v, Path)) Type guard
import stat
from pathlib import Path
def is_regular_non_symlink_file(value: object) -> bool:
if not isinstance(value, Path):
return False
try:
mode = value.lstat().st_mode
except OSError:
return False
return stat.S_ISREG(mode) and not stat.S_ISLNK(mode) Try / catch
try:
stage_evidence_bundle(dest, entries, secrets=secrets)
except SandboxError as exc:
if 'regular non-symlink' in str(exc):
# resolve and replace the offending entry with its real target bytes
bad = extract_path_from(exc)
entries[bad.name] = bad.resolve(strict=True).read_bytes()
raise Prevention
- Never stage symlinks; dereference and stage the target bytes.
- Add a CI check that rejects symlinks inside evidence directories.
- Use shutil.copyfile to materialize plain files before staging.
- For directories, enumerate and stage each regular file individually.
When it happens
Trigger: stage_evidence_bundle is handed a Path that lstat's as a symlink, a directory, a named pipe, a unix socket, a block/char device, or any non-regular inode.
Common situations: Evidence 'file' is actually a symlink created by a test harness for convenience; a directory was passed where a single file was expected; /tmp evidence path collided with an OS FIFO; a socket file was left in the workspace by a dev server; the proposer wrote evidence through a symlink farm.
Related errors
- evidence source must be a regular non-symlink file: {path}
- results directory must not traverse symlinks: {root}
- results artifact parent must be a real directory: {current}
- transcript artifact must be a regular non-symlink file: {pat
- {label} must be a real directory: {lexical}: {exc}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/9ab6a73885670ef8.
Report an issue: GitHub.