{"record":{"id":"f4e4f639ca34cfa4","repo":"abhigyanpatwari/GitNexus","slug":"overlay-destination-changed-while-being-read-tar","errorCode":null,"errorMessage":"overlay destination changed while being read: {target}","messagePattern":"overlay destination changed while being read: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/promotion_apply.py","lineNumber":130,"sourceCode":"    except FileNotFoundError as exc:\n        raise ValueError(f\"overlay destination must already be a regular file: {target}\") from exc\n    if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode):\n        raise ValueError(f\"overlay destination must already be a regular file: {target}\")\n    descriptor = os.open(path, os.O_RDONLY | getattr(os, \"O_NOFOLLOW\", 0))\n    try:\n        opened = os.fstat(descriptor)\n        if not stat.S_ISREG(opened.st_mode):\n            raise ValueError(f\"overlay destination must already be a regular file: {target}\")\n        chunks: list[bytes] = []\n        while chunk := os.read(descriptor, 64 * 1024):\n            chunks.append(chunk)\n        after = os.fstat(descriptor)\n    finally:\n        os.close(descriptor)\n    try:\n        final = path.lstat()\n    except FileNotFoundError as exc:\n        raise ValueError(f\"overlay destination changed while being read: {target}\") from exc\n\n    def identity(value: os.stat_result) -> tuple[int, int, int, int, int]:\n        return (\n            value.st_dev,\n            value.st_ino,\n            value.st_size,\n            value.st_mtime_ns,\n            stat.S_IMODE(value.st_mode),\n        )\n\n    if (\n        stat.S_ISLNK(final.st_mode)\n        or not stat.S_ISREG(final.st_mode)\n        or not (identity(before) == identity(opened) == identity(after) == identity(final))\n    ):\n        raise ValueError(f\"overlay destination changed while being read: {target}\")\n    return b\"\".join(chunks), opened.st_mode\n","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/promotion_apply.py#L112-L148","documentation":"After the read loop ends and the descriptor is closed, _read_destination lstat()s the path once more. If the file was removed during the read, FileNotFoundError is chained into this ValueError — proving the bytes you read are not currently attributable to the named path. The harness refuses to attest a result that lacks a stable path binding.","triggerScenarios":"path.lstat() at line 128 raises FileNotFoundError — someone unlinked the mirror between the read and the post-read lstat. Concurrent promotion rollback, `git clean`, or a destructive editor save.","commonSituations":"Concurrent `git checkout` / `rm` on the mirror during promotion; an IDE that saves by delete+rewrite; two promotion processes running against the same mirror set.","solutions":["Serialize promotions: only one process may touch the skill mirror roots at a time (use a file lock).","Stop background git operations and IDE file watchers on the repo during promotion.","Re-run promotion from a clean state once the contention is eliminated.","Add a repo-level lock (flock on a known file) around promotion_apply."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"# Acquire an exclusive lock on the mirror directory so nothing can unlink.\nimport fcntl, os\nfrom pathlib import Path\n\ndef lock_mirrors_root(root: Path) -> int:\n    fd = os.open(root, os.O_RDONLY)\n    fcntl.flock(fd, fcntl.LOCK_EX)\n    return fd  # keep open for the duration of promotion","typeGuard":null,"tryCatchPattern":"try:\n    data, mode = _read_destination(path, target=relative)\nexcept ValueError as exc:\n    if 'changed while being read' in str(exc) and isinstance(exc.__cause__, FileNotFoundError):\n        log.error('mirror %s unlinked during read; serialize promotions', relative)\n    raise","preventionTips":["Serialize promotions with an flock on the mirror root.","Stop git operations and IDE file watchers during promotion.","Never run two promotion processes against the same mirror set."],"tags":["overlay","promotion","toctou","mirror","filesystem","concurrency"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}