abhigyanpatwari/GitNexus · critical · ValueError

overlay destination parent changed during {phase}: {item['ta

Error message

overlay destination parent changed during {phase}: {item['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. `_validate_prepared_paths` re-opens each target's parent via `_open_target_parent` and compares its directory identity against the `parent_descriptor` captured during `_prepare_targets`. If the `(dev, ino, S_IFMT)` tuple differs, the parent directory was replaced between preparation and the revalidation at `phase`.

Source

Thrown at eval/workflow_bench/promotion_apply.py:273

    finally:
        os.close(reopened)


def _validate_prepared_paths(
    root: Path,
    root_descriptor: int,
    prepared: list[dict[str, Any]],
    *,
    phase: str,
) -> None:
    """Rebind every held parent descriptor to its current lexical repo path."""

    _validate_repository_root_binding(root, root_descriptor, phase=phase)
    for item in prepared:
        reopened = _open_target_parent(root_descriptor, item["target"])
        try:
            if _directory_identity(os.fstat(reopened)) != _directory_identity(os.fstat(item["parent_descriptor"])):
                raise ValueError(f"overlay destination parent changed during {phase}: {item['target']}")
        finally:
            os.close(reopened)
    # Catch a repository-root replacement that raced the parent walk itself.
    _validate_repository_root_binding(root, root_descriptor, phase=phase)


def _read_destination_at(
    parent_descriptor: int,
    name: str,
    *,
    target: PurePosixPath,
) -> tuple[bytes, int]:
    try:
        before = os.stat(name, dir_fd=parent_descriptor, follow_symlinks=False)
    except FileNotFoundError as exc:
        raise ValueError(f"overlay destination must already be a regular file: {target}") from exc
    if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode):
        raise ValueError(f"overlay destination must already be a regular file: {target}")

View on GitHub (pinned to d540b00184)

Solutions

  1. Ensure no process replaces target parent directories during the transaction.
  2. Hold an exclusive lock for the whole apply window.
  3. Re-run the full promotion 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 during' in str(exc):
        raise RuntimeError(f'target parent replaced mid-transaction at {exc}') from exc

Prevention

When it happens

Trigger: A target's parent directory was replaced (rm+mkdir, mv) between the prepare pass and the pre-publication / publication / post-apply revalidation.

Common situations: A skill category dir recreated by another tool mid-apply; overlapping promotion or build rewriting the tree; checkout reset partway through apply.

Related errors


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