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

  1. Make the overlay read-only and stable for the duration of the run: chmod -R a-w, ensure no concurrent process writes it.
  2. Check file permissions (chmod/chown) — the runner's uid must have read access.
  3. Re-stage the overlay from source if files were transient.
  4. 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

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


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