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
- Ensure no process replaces target parent directories during the transaction.
- Hold an exclusive lock for the whole apply window.
- 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
- Serialize all writers to the skill mirror tree with one lock.
- Re-run the entire transaction after stabilizing the tree; never resume mid-way.
- Audit for tooling that recreates skill directories during promotion.
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
- repository root changed during overlay {phase}: {root}
- overlay destination parent changed while opening: {target}
- oracle stage root changed during verification
- oracle file changed during verification: {item.target}
- frozen overlay bytes do not match the authorized input
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/171d047d1eb63ec1.
Report an issue: GitHub.