abhigyanpatwari/GitNexus · error · ValueError

candidate overlay directory is unreadable: {directory}: {exc

Error message

candidate overlay directory is unreadable: {directory}: {exc}

What it means

Thrown by candidate_overlay_files (evolution.py:291) when os.scandir of a directory inside the overlay tree raises OSError during the recursive walk. The top-level directory is already validated; this fires on a subdirectory that cannot be listed — typically a permissions problem (missing read or execute bit) or an I/O error.

Source

Thrown at eval/workflow_bench/evolution.py:291

    overlay = overlay.expanduser().absolute()
    try:
        resolved_overlay = overlay.resolve(strict=True)
    except OSError as exc:
        raise ValueError(f"candidate overlay is not a directory: {overlay}") from exc
    if resolved_overlay != overlay:
        raise ValueError(f"candidate overlay cannot traverse symlinks: {overlay}")
    _require_real_directory(overlay, label="candidate overlay")

    entries: list[Path] = []
    pending = [overlay]
    entry_count = 0
    while pending:
        directory = pending.pop()
        child_directories: list[Path] = []
        try:
            iterator = os.scandir(directory)
        except OSError as exc:
            raise ValueError(f"candidate overlay directory is unreadable: {directory}: {exc}") from exc
        with iterator:
            for item in iterator:
                entry_count += 1
                if entry_count > MAX_CANDIDATE_ENTRIES:
                    raise ValueError(f"candidate overlay exceeds the {MAX_CANDIDATE_ENTRIES}-entry limit")
                path = Path(item.path)
                relative = path.relative_to(overlay)
                if len(relative.as_posix().encode()) > MAX_CANDIDATE_PATH_BYTES:
                    raise ValueError(f"candidate overlay path exceeds {MAX_CANDIDATE_PATH_BYTES} bytes: {relative}")
                if item.is_symlink():
                    raise ValueError(f"candidate overlay cannot contain symlinks: {relative}")
                if item.is_dir(follow_symlinks=False):
                    child_directories.append(path)
                    continue
                if not item.is_file(follow_symlinks=False):
                    raise ValueError(f"candidate overlay entries must be regular files: {relative}")
                entries.append(path)
                if len(entries) > MAX_CANDIDATE_FILES:

View on GitHub (pinned to d540b00184)

Solutions

  1. Fix directory permissions across the overlay so every dir is readable+traversable: find overlay -type d -exec chmod u+rx {} +.
  2. Re-copy the overlay preserving directory execute bits.
  3. Move the overlay onto a healthy local filesystem.

Example fix

# before: a subdir is unreadable -> scandir raises EACCES

# after: ensure every directory is r-x before running
import subprocess
subprocess.run(['find', str(overlay), '-type', 'd', '-exec', 'chmod', 'u+rxX', {} +'])
# or in Python:
for d in overlay.rglob('*'):
    if d.is_dir():
        d.chmod(0o755)
Defensive patterns

Strategy: validation

Validate before calling

import os
from pathlib import Path

def overlay_dirs_listable(root: Path) -> bool:
    for d in [root, *root.rglob('*')]:
        if d.is_dir():
            try:
                list(os.scandir(d))
            except OSError:
                return False
    return True

Type guard

null

Try / catch

try:
    apply_candidate_overlay(overlay, worktree, sandbox=sandbox)
except ValueError as exc:
    if 'directory is unreadable' in str(exc):
        # chmod u+rx on overlay dirs, then retry
        ...

Prevention

When it happens

Trigger: A subdirectory of the overlay lacks read or execute permission (EACCES), or scandir hits an I/O error mid-walk (failing disk, vanished mount).

Common situations: Overlay created with restrictive umask leaving a subdir mode 0o300 or 0o600; copy from a source that dropped execute bits on directories; overlay partially on an unmounted network share.

Related errors


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