{"record":{"id":"e25b383a15269102","repo":"affaan-m/ECC","slug":"reference-source-mutated-while-creating-stable-snapshot","errorCode":null,"errorMessage":"reference source mutated while creating stable snapshot","messagePattern":"reference source mutated while creating stable snapshot","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/taste-application/scripts/tasteforge/workflow.py","lineNumber":97,"sourceCode":"                    if not chunk:\n                        break\n                    total += len(chunk)\n                    digest.update(chunk)\n                    view = memoryview(chunk)\n                    while view:\n                        written = os.write(snapshot_fd, view)\n                        if written <= 0:\n                            raise ValueError(\"stable source snapshot write made no progress\")\n                        view = view[written:]\n                os.fsync(snapshot_fd)\n            finally:\n                os.close(snapshot_fd)\n            source_digest = digest.hexdigest()\n            copied = os.fstat(descriptor)\n            if (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns) != (\n                copied.st_dev, copied.st_ino, copied.st_size, copied.st_mtime_ns\n            ):\n                raise ValueError(\"reference source mutated while creating stable snapshot\")\n            verified_size, verified_digest = _hash_descriptor(descriptor)\n            if verified_size != total or verified_digest != source_digest:\n                raise ValueError(\"reference source mutated while creating stable snapshot\")\n\n            measured = probe(snapshot)\n\n            after = os.fstat(descriptor)\n            final_size, final_digest = _hash_descriptor(descriptor)\n            if (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns) != (\n                after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns\n            ) or final_size != total or final_digest != source_digest:\n                raise ValueError(\"reference source mutated during media probing\")\n            rebound = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)\n            try:\n                rebound_stat = os.fstat(rebound)\n                if (rebound_stat.st_dev, rebound_stat.st_ino) != (before.st_dev, before.st_ino):\n                    raise ValueError(\"reference source identity changed during media probing\")\n            finally:","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/skills/taste-application/scripts/tasteforge/workflow.py#L79-L115","documentation":"After writing the snapshot, _stable_probe re-fstats the source descriptor and compares dev/ino/size/mtime_ns against the values captured before copying. Any difference means the file was modified while the snapshot was being created, so the digest would not describe one stable source object. It then re-hashes the descriptor and compares against the streamed digest as a second check, raising the same error on mismatch. This is the core TOCTOU protection of the 'stable probe' contract.","triggerScenarios":"The source file is written, truncated, appended to, or replaced (size or mtime_ns change) between the pre-copy fstat and the post-copy fstat/re-hash; or the re-hashed digest of the fd differs from the digest accumulated during streaming.","commonSituations":"An editor auto-save or formatter (e.g. black, Prettier) touches the file during the probe; a build step or dev server regenerates the file concurrently; the file is rewritten via rename while an old inode is still being read; or filesystem timestamp granularity/storage shuffles metadata.","solutions":["Rerun the workflow with nothing else writing the source file (stop watchers, formatters-on-save, codegen).","Copy the source to a scratch location, freeze it, and probe the copy.","If the file is legitimately rebuilt on save, exclude it from the probe target or pause the watcher for the run.","If this fires deterministically with no concurrent writer, compare st_mtime_ns precision behavior of your filesystem and report the repro."],"exampleFix":"// before (concurrent writer: dev server regenerates source)\npython workflow.py probe src/generated.py  # fails\n\n// after (freeze a copy first)\ncp src/generated.py /tmp/frozen.py\npython workflow.py probe /tmp/frozen.py","handlingStrategy":"fallback","validationCode":"import hashlib, os\ndef file_fingerprint(path):\n    st = os.stat(path)\n    return (st.st_dev, st.st_ino, st.st_size, st.st_mtime_ns)\n\nbefore = file_fingerprint(path)  # compare after run; retry if changed","typeGuard":null,"tryCatchPattern":"import time\nfor attempt in range(3):\n    try:\n        return run_workflow(path, probe)\n    except ValueError as e:\n        if \"mutated\" in str(e) and attempt < 2:\n            time.sleep(1)  # let the concurrent writer finish, then retry\n            continue\n        raise","preventionTips":["Stop file watchers, formatters-on-save, and codegen while probing.","Probe a frozen copy of the source instead of a live tree file.","Avoid running the tool during git operations that touch the file.","Retry once or twice on this error — transient writers often finish quickly."],"tags":["filesystem","concurrency","toccu","integrity"],"backgroundTag":"checksum-mismatch","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}