abhigyanpatwari/GitNexus · error · ValueError

overlay destination parent changed while opening: {target}

Error message

overlay destination parent changed while opening: {target}

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. `os.open(part, flags, dir_fd=current)` (with `O_NOFOLLOW | O_DIRECTORY`) raised `OSError` after `os.stat(part, ...)` succeeded moments before. Between the stat and the open, the intermediate component was replaced/deleted/changed type — a TOCTOU race on a parent directory component.

Source

Thrown at eval/workflow_bench/promotion_apply.py:207


def _open_target_parent(root_descriptor: int, target: PurePosixPath) -> int:
    if target.is_absolute() or not target.parts or ".." in target.parts:
        raise ValueError(f"overlay destination escapes repository: {target}")
    flags = os.O_RDONLY | os.O_DIRECTORY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0)
    current = os.dup(root_descriptor)
    try:
        for part in target.parts[:-1]:
            try:
                metadata = os.stat(part, dir_fd=current, follow_symlinks=False)
            except OSError as exc:
                raise ValueError(f"overlay destination parent is unavailable: {target}") from exc
            if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):
                raise ValueError(f"overlay destination parent must not be a symlink: {target}")
            try:
                child = os.open(part, flags, dir_fd=current)
            except OSError as exc:
                raise ValueError(f"overlay destination parent changed while opening: {target}") from exc
            opened = os.fstat(child)
            if (
                opened.st_dev,
                opened.st_ino,
                stat.S_IFMT(opened.st_mode),
            ) != (
                metadata.st_dev,
                metadata.st_ino,
                stat.S_IFMT(metadata.st_mode),
            ):
                os.close(child)
                raise ValueError(f"overlay destination parent changed while opening: {target}")
            os.close(current)
            current = child
        return current
    except BaseException:
        os.close(current)
        raise

View on GitHub (pinned to d540b00184)

Solutions

  1. Ensure no other process mutates the skill mirror tree during the call.
  2. Hold an exclusive lock around the checkout for the whole transaction.
  3. Retry the full entry once from a stable, single-writer checkout.
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 'parent changed while opening' in str(exc):
        log.warning('parent component replaced mid-walk; retrying once')
        apply_promoted_overlay(overlay, repo_root=root)

Prevention

When it happens

Trigger: A concurrent process replaced or removed an intermediate directory component between the per-component `os.stat` and the `os.open` in `_open_target_parent`.

Common situations: Concurrent `git checkout`/`rm -rf`/`mv` of a skill directory during promotion; two overlapping promotions; editor or indexer rewriting the tree.

Related errors


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