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

  1. Ensure the repository root stays present and unchanged for the whole transaction.
  2. Use an exclusive lock and a private, stable checkout.
  3. 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

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


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