abhigyanpatwari/GitNexus · error · ValueError

{label} changed while being read: {path}

Error message

{label} changed while being read: {path}

What it means

Thrown by _bounded_regular_bytes at the open-time identity check (the only raise with this exact message, evolution.py:102). After lstat (line 90) and os.open with O_NOFOLLOW (line 98), an fstat is compared against the pre-open metadata: the descriptor must still be a regular file with identical st_dev/st_ino. If not, the file was replaced, renamed, or turned into a non-regular file in the window between stat and open — a TOCTOU condition the harness refuses to silently absorb.

Source

Thrown at eval/workflow_bench/evolution.py:126

                break
            chunks.append(chunk)
            remaining -= len(chunk)
        content = b"".join(chunks)
        if len(content) > limit:
            raise ValueError(f"{label} exceeds the bounded evidence limit")
        after = os.fstat(descriptor)
        if (
            opened.st_dev,
            opened.st_ino,
            opened.st_size,
            opened.st_mtime_ns,
        ) != (
            after.st_dev,
            after.st_ino,
            after.st_size,
            after.st_mtime_ns,
        ) or len(content) != opened.st_size:
            raise ValueError(f"{label} changed while being read: {path}")
        return content
    finally:
        os.close(descriptor)


def candidate_overlay_payload(overlay: Path) -> tuple[str, list[tuple[PurePosixPath, bytes]]]:
    """Return the sole validated, bounded candidate payload and its digest."""

    root = overlay.expanduser().absolute()
    payload: list[tuple[PurePosixPath, bytes]] = []
    remaining = MAX_CANDIDATE_OVERLAY_BYTES
    for source in candidate_overlay_files(root):
        relative = PurePosixPath(source.relative_to(root).as_posix())
        _require_directory_chain(
            root,
            Path(*relative.parent.parts),
            label="candidate overlay directory",
        )

View on GitHub (pinned to d540b00184)

Solutions

  1. Copy the overlay to a private, read-only directory and pass that copy to the harness so no other writer can race it.
  2. Stop all concurrent editors/formatters/IDEs touching the overlay during the run.
  3. If transient (filesystem jitter), snapshot the overlay with shutil.copytree into a tempfile.mkdtemp and retry.
  4. Make the overlay tree read-only (chmod -R a-w) before invoking the harness.

Example fix

# before: pass a live overlay dir an editor may touch
apply_candidate_overlay(Path('overlay'), worktree, sandbox=sandbox)

# after: snapshot to a private dir first
import tempfile, shutil
snap = Path(tempfile.mkdtemp(prefix='wfbench-overlay-snap-'))
shutil.copytree('overlay', snap, dirs_exist_ok=False)
for p in snap.rglob('*'): p.chmod(0o500)
apply_candidate_overlay(snap, worktree, sandbox=sandbox)
Defensive patterns

Strategy: validation

Validate before calling

import os, stat, shutil, tempfile
from pathlib import Path

def freeze_overlay(src: Path) -> Path:
    """Copy overlay to a private real dir and make it read-only to defeat TOCTOU."""
    snap = Path(tempfile.mkdtemp(prefix='wfbench-snap-'))
    shutil.copytree(src, snap / 'overlay')
    for p in (snap / 'overlay').rglob('*'):
        p.chmod(0o500 if p.is_dir() else 0o400)
    return snap / 'overlay'

Type guard

null

Try / catch

try:
    apply_candidate_overlay(overlay, worktree, sandbox=sandbox)
except ValueError as exc:
    if 'changed while opening' in str(exc):
        overlay = freeze_overlay(original_overlay)  # then retry once
        apply_candidate_overlay(overlay, worktree, sandbox=sandbox)

Prevention

When it happens

Trigger: Another process replaces/rename the overlay file between the lstat and the O_NOFOLLOW open; an editor or formatter rewrites the file mid-run; a symlink swap attack against the overlay path; the file is deleted and recreated concurrently.

Common situations: Running the benchmark while an IDE/formatter/git checkout rewrites the overlay; pointing the overlay at a directory under active sync (Dropbox/network FS); a CI step that regenerates the overlay concurrently with evaluation.

Related errors


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