abhigyanpatwari/GitNexus · error · ValueError
repository root changed while opening: {root}
Error message
repository root changed while opening: {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. `_open_repository_root` calls `os.open(root, flags)` with `O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW` after `lstat()`/`resolve()` succeeded. If that `os.open` raises `OSError`, the path changed between the stat and the open — a TOCTOU window where the root was replaced, deleted, had its permissions changed, or was swapped to a symlink.
Source
Thrown at eval/workflow_bench/promotion_apply.py:165
return b"".join(chunks), opened.st_mode
def _open_repository_root(repo_root: Path) -> tuple[Path, int]:
root = repo_root.expanduser().absolute()
try:
metadata = root.lstat()
resolved = root.resolve(strict=True)
except OSError as exc:
raise ValueError(f"repository root is unavailable: {root}") from exc
if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):
raise ValueError(f"repository root must be a real directory: {root}")
if resolved != root:
raise ValueError(f"repository root must not traverse symlinks: {root}")
flags = os.O_RDONLY | os.O_DIRECTORY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0)
try:
descriptor = os.open(root, flags)
except OSError as exc:
raise ValueError(f"repository root changed while opening: {root}") from exc
try:
opened = os.fstat(descriptor)
final = root.lstat()
final_resolved = root.resolve(strict=True)
def identity(value: os.stat_result) -> tuple[int, int, int]:
return value.st_dev, value.st_ino, stat.S_IFMT(value.st_mode)
if (
stat.S_ISLNK(final.st_mode)
or not stat.S_ISDIR(opened.st_mode)
or not stat.S_ISDIR(final.st_mode)
or final_resolved != root
or not (identity(metadata) == identity(opened) == identity(final))
):
raise ValueError(f"repository root changed while opening: {root}")
except OSError as exc:
os.close(descriptor)View on GitHub (pinned to d540b00184)
Solutions
- Ensure no other process mutates the repository root during the call.
- Run promotion against an exclusive checkout (one job, one clone).
- Retry the whole public entry once from a known-good root; if it persists, treat as an environment fault, not a data fault.
Example fix
// before
apply_promoted_overlay(overlay, repo_root=root)
// after
def run_with_retry(fn, attempts=3):
for i in range(attempts):
try:
return fn()
except ValueError as exc:
if 'changed while opening' in str(exc) and i < attempts-1:
continue
raise
run_with_retry(lambda: apply_promoted_overlay(overlay, repo_root=root)) Defensive patterns
Strategy: retry
Validate before calling
# No pure pre-validation can defeat a race; the closest guard is an exclusive lock.
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 while opening' in str(exc):
log.warning('root replaced during open; retrying once after stabilization')
time.sleep(0.2)
apply_promoted_overlay(overlay, repo_root=root) Prevention
- Serialize promotions with an flock/lockfile around the checkout.
- Never run two concurrent promotions against the same root.
- Use a local filesystem (not NFS) for the checkout during promotion.
When it happens
Trigger: A concurrent process `mv`/`rm -rf`/`chmod` of the repository root between the initial `lstat` and the `os.open`; the root is on a filesystem that returns ESTALE (NFS) mid-call.
Common situations: Two CI jobs sharing one checkout; an operator re-cloning/`git clean -fdx` during a promotion; network filesystem eviction; container filesystem teardown racing the call.
Related errors
- repository root changed during overlay {phase}: {root}
- overlay destination parent changed while opening: {target}
- overlay destination parent changed during {phase}: {item['ta
- Analyzer runtime payload directory is unavailable: ${absolut
- oracle source is unreadable: {relative}
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/6379e07806ee424a.
Report an issue: GitHub.