{"record":{"id":"e63501d83ff77464","repo":"abhigyanpatwari/GitNexus","slug":"label-changed-while-being-read-path","errorCode":null,"errorMessage":"{label} changed while being read: {path}","messagePattern":"(.+?) changed while being read: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/evolution.py","lineNumber":126,"sourceCode":"                break\n            chunks.append(chunk)\n            remaining -= len(chunk)\n        content = b\"\".join(chunks)\n        if len(content) > limit:\n            raise ValueError(f\"{label} exceeds the bounded evidence limit\")\n        after = os.fstat(descriptor)\n        if (\n            opened.st_dev,\n            opened.st_ino,\n            opened.st_size,\n            opened.st_mtime_ns,\n        ) != (\n            after.st_dev,\n            after.st_ino,\n            after.st_size,\n            after.st_mtime_ns,\n        ) or len(content) != opened.st_size:\n            raise ValueError(f\"{label} changed while being read: {path}\")\n        return content\n    finally:\n        os.close(descriptor)\n\n\ndef candidate_overlay_payload(overlay: Path) -> tuple[str, list[tuple[PurePosixPath, bytes]]]:\n    \"\"\"Return the sole validated, bounded candidate payload and its digest.\"\"\"\n\n    root = overlay.expanduser().absolute()\n    payload: list[tuple[PurePosixPath, bytes]] = []\n    remaining = MAX_CANDIDATE_OVERLAY_BYTES\n    for source in candidate_overlay_files(root):\n        relative = PurePosixPath(source.relative_to(root).as_posix())\n        _require_directory_chain(\n            root,\n            Path(*relative.parent.parts),\n            label=\"candidate overlay directory\",\n        )","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/evolution.py#L108-L144","documentation":"Thrown by _bounded_regular_bytes at the open-time identity check (the only raise with this exact message, evolution.py:102). After lstat (line 90) and os.open with O_NOFOLLOW (line 98), an fstat is compared against the pre-open metadata: the descriptor must still be a regular file with identical st_dev/st_ino. If not, the file was replaced, renamed, or turned into a non-regular file in the window between stat and open — a TOCTOU condition the harness refuses to silently absorb.","triggerScenarios":"Another process replaces/rename the overlay file between the lstat and the O_NOFOLLOW open; an editor or formatter rewrites the file mid-run; a symlink swap attack against the overlay path; the file is deleted and recreated concurrently.","commonSituations":"Running the benchmark while an IDE/formatter/git checkout rewrites the overlay; pointing the overlay at a directory under active sync (Dropbox/network FS); a CI step that regenerates the overlay concurrently with evaluation.","solutions":["Copy the overlay to a private, read-only directory and pass that copy to the harness so no other writer can race it.","Stop all concurrent editors/formatters/IDEs touching the overlay during the run.","If transient (filesystem jitter), snapshot the overlay with shutil.copytree into a tempfile.mkdtemp and retry.","Make the overlay tree read-only (chmod -R a-w) before invoking the harness."],"exampleFix":"# before: pass a live overlay dir an editor may touch\napply_candidate_overlay(Path('overlay'), worktree, sandbox=sandbox)\n\n# after: snapshot to a private dir first\nimport tempfile, shutil\nsnap = Path(tempfile.mkdtemp(prefix='wfbench-overlay-snap-'))\nshutil.copytree('overlay', snap, dirs_exist_ok=False)\nfor p in snap.rglob('*'): p.chmod(0o500)\napply_candidate_overlay(snap, worktree, sandbox=sandbox)","handlingStrategy":"validation","validationCode":"import os, stat, shutil, tempfile\nfrom pathlib import Path\n\ndef freeze_overlay(src: Path) -> Path:\n    \"\"\"Copy overlay to a private real dir and make it read-only to defeat TOCTOU.\"\"\"\n    snap = Path(tempfile.mkdtemp(prefix='wfbench-snap-'))\n    shutil.copytree(src, snap / 'overlay')\n    for p in (snap / 'overlay').rglob('*'):\n        p.chmod(0o500 if p.is_dir() else 0o400)\n    return snap / 'overlay'","typeGuard":"null","tryCatchPattern":"try:\n    apply_candidate_overlay(overlay, worktree, sandbox=sandbox)\nexcept ValueError as exc:\n    if 'changed while opening' in str(exc):\n        overlay = freeze_overlay(original_overlay)  # then retry once\n        apply_candidate_overlay(overlay, worktree, sandbox=sandbox)","preventionTips":["Never point the harness at an overlay an editor/formatter may rewrite.","Snapshot the overlay into a private read-only directory before each run.","On shared/cloud filesystems, copy the overlay to local disk first."],"tags":["toctou","concurrency","security","overlay","evidence"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}