{"record":{"id":"fe1c886ba73404b5","repo":"abhigyanpatwari/GitNexus","slug":"overlay-destination-parent-changed-while-opening","errorCode":null,"errorMessage":"overlay destination parent changed while opening: {target}","messagePattern":"overlay destination parent changed while opening: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/promotion_apply.py","lineNumber":207,"sourceCode":"\n\ndef _open_target_parent(root_descriptor: int, target: PurePosixPath) -> int:\n    if target.is_absolute() or not target.parts or \"..\" in target.parts:\n        raise ValueError(f\"overlay destination escapes repository: {target}\")\n    flags = os.O_RDONLY | os.O_DIRECTORY | getattr(os, \"O_CLOEXEC\", 0) | getattr(os, \"O_NOFOLLOW\", 0)\n    current = os.dup(root_descriptor)\n    try:\n        for part in target.parts[:-1]:\n            try:\n                metadata = os.stat(part, dir_fd=current, follow_symlinks=False)\n            except OSError as exc:\n                raise ValueError(f\"overlay destination parent is unavailable: {target}\") from exc\n            if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):\n                raise ValueError(f\"overlay destination parent must not be a symlink: {target}\")\n            try:\n                child = os.open(part, flags, dir_fd=current)\n            except OSError as exc:\n                raise ValueError(f\"overlay destination parent changed while opening: {target}\") from exc\n            opened = os.fstat(child)\n            if (\n                opened.st_dev,\n                opened.st_ino,\n                stat.S_IFMT(opened.st_mode),\n            ) != (\n                metadata.st_dev,\n                metadata.st_ino,\n                stat.S_IFMT(metadata.st_mode),\n            ):\n                os.close(child)\n                raise ValueError(f\"overlay destination parent changed while opening: {target}\")\n            os.close(current)\n            current = child\n        return current\n    except BaseException:\n        os.close(current)\n        raise","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/promotion_apply.py#L189-L225","documentation":"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.","triggerScenarios":"A concurrent process replaced or removed an intermediate directory component between the per-component `os.stat` and the `os.open` in `_open_target_parent`.","commonSituations":"Concurrent `git checkout`/`rm -rf`/`mv` of a skill directory during promotion; two overlapping promotions; editor or indexer rewriting the tree.","solutions":["Ensure no other process mutates the skill mirror tree during the call.","Hold an exclusive lock around the checkout for the whole transaction.","Retry the full entry once from a stable, single-writer checkout."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"import fcntl\nwith open(root / '.promotion.lock', 'w') as lock:\n    fcntl.flock(lock, fcntl.LOCK_EX)\n    apply_promoted_overlay(overlay, repo_root=root)","typeGuard":null,"tryCatchPattern":"except ValueError as exc:\n    if 'parent changed while opening' in str(exc):\n        log.warning('parent component replaced mid-walk; retrying once')\n        apply_promoted_overlay(overlay, repo_root=root)","preventionTips":["Serialize all promotions and tree mutations with a shared lockfile.","Do not run an editor/indexer over the skill tree during promotion.","Retry the whole transaction, not a partial sub-step."],"tags":["toctou","filesystem","race-condition","overlay"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}