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

  1. Ensure no other process mutates the repository root during the call.
  2. Run promotion against an exclusive checkout (one job, one clone).
  3. 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

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


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