abhigyanpatwari/GitNexus · error · ValueError
{label} is unreadable: {path}: {exc}
Error message
{label} is unreadable: {path}: {exc} What it means
Thrown by _bounded_regular_bytes() in eval/workflow_bench/evolution.py when path.lstat() raises OSError while trying to read a candidate evidence file: the file does not exist, was removed between directory walk and read, or permission was denied. The original OSError is chained.
Source
Thrown at eval/workflow_bench/evolution.py:92
def _require_directory_chain(root: Path, relative: Path, *, label: str) -> None:
"""Validate each lexical directory without erasing links via resolve()."""
_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)View on GitHub (pinned to d540b00184)
Solutions
- Make the overlay read-only and stable for the duration of the run: chmod -R a-w, ensure no concurrent process writes it.
- Check file permissions (chmod/chown) — the runner's uid must have read access.
- Re-stage the overlay from source if files were transient.
- If racing with another process, run the bench against a private copy (cp -rL).
Example fix
# before: overlay mutated while being read
# ValueError: candidate overlay file is unreadable: /tmp/ov/x.yaml: [Errno 2] No such file
# after: snapshot + freeze before the run
cp -rL /volatile/overlay /tmp/ov && chmod -R a-w /tmp/ov
candidate_overlay_payload(Path('/tmp/ov')) Defensive patterns
Strategy: validation
Validate before calling
from pathlib import Path
def ensure_readable_file(p: Path) -> None:
if not p.is_file() or not os.access(p, os.R_OK):
raise SystemExit(f'evidence file missing/unreadable: {p}')
# call before _bounded_regular_bytes; also freeze the overlay to avoid races Type guard
def is_unreadable_error(exc: ValueError) -> bool:
return 'is unreadable:' in str(exc) Try / catch
try:
_bounded_regular_bytes(p, limit=L, label='candidate overlay file')
except ValueError as e:
if is_unreadable_error(e):
# restage overlay read-only and retry
raise
raise Prevention
- Make overlays read-only for the duration of the read (chmod -R a-w).
- Ensure the runner uid has read permission on every evidence file.
- Do not run other writers against the overlay during the bench.
When it happens
Trigger: candidate_overlay_payload() enumerates files, then for each source calls _bounded_regular_bytes(); if the file vanished or is unreadable in between (TOCTOU on existence), lstat raises and you get this error. Also: a file the runner has no read permission on.
Common situations: Concurrent writer/deleter racing with the bench reader; permissions stripped on the file after enumeration; an overlay file that is a broken symlink (lstat succeeds but is a link — different error) or was unlinked mid-run; runner uid lacks read permission.
Related errors
- {label} is unavailable: {path}: {exc}
- {label} must be a real non-symlink directory: {path}
- {label} must be a regular non-symlink file: {path}
- {label} changed while opening: {path}
- {label} contains an unsafe path component: {relative}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/f980c90bc2b50b70.
Report an issue: GitHub.