abhigyanpatwari/GitNexus · error · ValueError
overlay destination changed while being read: {target}
Error message
overlay destination changed while being read: {target} What it means
After the read loop ends and the descriptor is closed, _read_destination lstat()s the path once more. If the file was removed during the read, FileNotFoundError is chained into this ValueError — proving the bytes you read are not currently attributable to the named path. The harness refuses to attest a result that lacks a stable path binding.
Source
Thrown at eval/workflow_bench/promotion_apply.py:130
except FileNotFoundError as exc:
raise ValueError(f"overlay destination must already be a regular file: {target}") from exc
if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode):
raise ValueError(f"overlay destination must already be a regular file: {target}")
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):
raise ValueError(f"overlay destination must already be a regular file: {target}")
chunks: list[bytes] = []
while chunk := os.read(descriptor, 64 * 1024):
chunks.append(chunk)
after = os.fstat(descriptor)
finally:
os.close(descriptor)
try:
final = path.lstat()
except FileNotFoundError as exc:
raise ValueError(f"overlay destination changed while being read: {target}") from exc
def identity(value: os.stat_result) -> tuple[int, int, int, int, int]:
return (
value.st_dev,
value.st_ino,
value.st_size,
value.st_mtime_ns,
stat.S_IMODE(value.st_mode),
)
if (
stat.S_ISLNK(final.st_mode)
or not stat.S_ISREG(final.st_mode)
or not (identity(before) == identity(opened) == identity(after) == identity(final))
):
raise ValueError(f"overlay destination changed while being read: {target}")
return b"".join(chunks), opened.st_mode
View on GitHub (pinned to d540b00184)
Solutions
- Serialize promotions: only one process may touch the skill mirror roots at a time (use a file lock).
- Stop background git operations and IDE file watchers on the repo during promotion.
- Re-run promotion from a clean state once the contention is eliminated.
- Add a repo-level lock (flock on a known file) around promotion_apply.
Defensive patterns
Strategy: try-catch
Validate before calling
# Acquire an exclusive lock on the mirror directory so nothing can unlink.
import fcntl, os
from pathlib import Path
def lock_mirrors_root(root: Path) -> int:
fd = os.open(root, os.O_RDONLY)
fcntl.flock(fd, fcntl.LOCK_EX)
return fd # keep open for the duration of promotion Try / catch
try:
data, mode = _read_destination(path, target=relative)
except ValueError as exc:
if 'changed while being read' in str(exc) and isinstance(exc.__cause__, FileNotFoundError):
log.error('mirror %s unlinked during read; serialize promotions', relative)
raise Prevention
- Serialize promotions with an flock on the mirror root.
- Stop git operations and IDE file watchers during promotion.
- Never run two promotion processes against the same mirror set.
When it happens
Trigger: path.lstat() at line 128 raises FileNotFoundError — someone unlinked the mirror between the read and the post-read lstat. Concurrent promotion rollback, `git clean`, or a destructive editor save.
Common situations: Concurrent `git checkout` / `rm` on the mirror during promotion; an IDE that saves by delete+rewrite; two promotion processes running against the same mirror set.
Related errors
- overlay destination must already be a regular file: {target}
- {label} changed while being read: {path}
- overlay snapshot destination already exists: {destination}
- frozen overlay bytes do not match the authorized input
- overlay destination parent changed while opening: {target}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/f4e4f639ca34cfa4.
Report an issue: GitHub.