abhigyanpatwari/GitNexus · critical · ValueError
repository root changed during overlay {phase}: {root}
Error message
repository root changed during overlay {phase}: {root} What it means
All errors below are raised by internal helpers of `eval/workflow_bench/promotion_apply.py` and propagate to the caller of the public entry points: `apply_promoted_overlay(overlay, repo_root, *, expected_digest, expected_target_bases)`, `destination_base_digests(overlay, repo_root)`, `committed_destination_base_digests(overlay, repo_root, *, ref)` and `freeze_overlay(overlay, destination)`. The module applies promoted skill overlays across the canonical skill tree plus its shipped mirrors (`gitnexus/skills`, `gitnexus-claude-plugin/skills`) in a TOCTOU-hardened, symlink-rejecting, descriptor-bound transaction. `_validate_repository_root_binding(root, root_descriptor, *, phase)` re-checks the root mid-transaction by calling `root.lstat()`, `root.resolve(strict=True)`, and `os.open(root, flags)` again. If any of those raises `OSError`, the root became inaccessible or was replaced while the overlay transaction was already in progress (the `phase` name appears in the message).
Source
Thrown at eval/workflow_bench/promotion_apply.py:241
except BaseException:
os.close(current)
raise
def _directory_identity(metadata: os.stat_result) -> tuple[int, int, int]:
return metadata.st_dev, metadata.st_ino, stat.S_IFMT(metadata.st_mode)
def _validate_repository_root_binding(root: Path, root_descriptor: int, *, phase: str) -> None:
"""Prove the held root still names the repository's lexical directory."""
flags = os.O_RDONLY | os.O_DIRECTORY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0)
try:
lexical = root.lstat()
resolved = root.resolve(strict=True)
reopened = os.open(root, flags)
except OSError as exc:
raise ValueError(f"repository root changed during overlay {phase}: {root}") from exc
try:
opened = os.fstat(reopened)
held = os.fstat(root_descriptor)
if (
stat.S_ISLNK(lexical.st_mode)
or not stat.S_ISDIR(lexical.st_mode)
or resolved != root
or not stat.S_ISDIR(opened.st_mode)
or not stat.S_ISDIR(held.st_mode)
or _directory_identity(lexical) != _directory_identity(opened)
or _directory_identity(opened) != _directory_identity(held)
):
raise ValueError(f"repository root changed during overlay {phase}: {root}")
finally:
os.close(reopened)
def _validate_prepared_paths(View on GitHub (pinned to d540b00184)
Solutions
- Ensure the repository root stays present and unchanged for the whole transaction.
- Use an exclusive lock and a private, stable checkout.
- Re-run from a fresh trusted clone; do not attempt partial recovery by hand.
Defensive patterns
Strategy: retry
Validate before calling
import fcntl
with open(root / '.promotion.lock', 'w') as lock:
fcntl.flock(lock, fcntl.LOCK_EX)
apply_promoted_overlay(overlay, repo_root=root) Try / catch
except ValueError as exc:
if 'changed during overlay' in str(exc):
raise RuntimeError(f'root replaced mid-transaction at {exc}; re-run from a clean clone') from exc Prevention
- Keep the root stable (no reset/teardown) for the full apply window.
- Hold an exclusive lock for the whole transaction.
- Abort and re-clone rather than retrying in-place on a shared checkout.
When it happens
Trigger: Called from `_validate_prepared_paths` at preparation, pre-publication, publication, and post-apply phases; any concurrent removal/replacement/permission-change of the root during the transaction trips it.
Common situations: A long promotion interrupted by a checkout reset; CI timeout tearing down the workspace mid-run; operator `rm -rf` during apply; mount eviction mid-transaction.
Related errors
- overlay destination parent changed during {phase}: {item['ta
- repository root changed while opening: {root}
- oracle stage root changed during verification
- oracle file changed during verification: {item.target}
- overlay destination parent changed while opening: {target}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/1f61705e3e8511f4.
Report an issue: GitHub.