affaan-m/ECC · error · ValueError

reference source identity changed during media probing

Error message

reference source identity changed during media probing

What it means

After probing, _stable_probe re-opens the reference path with O_NOFOLLOW and compares the new file's (device, inode) against the originally opened file. If they differ, the path now points at a different file object — the original was replaced by rename/unlink-and-recreate or the path's symlink was swapped — so the digest recorded for the original file no longer describes what lives at that path. The workflow raises to keep the SHA-256 in receipts and manifests bound to a stable identity.

Solutions

  1. Ensure no process renames, deletes, or recreates files under the references directory while run_workflow executes.
  2. Freeze (snapshot/copy) the reference set before the run and point the contract config at the frozen copies.
  3. If symlinks are used, replace them with real copies before probing, or keep the link target stable during the run.
  4. Re-run the workflow after the replacing process finishes; on a stable path the identity check passes.
  5. If a swap was intentional, treat it as a new reference and recompute provenance from the new file.

Example fix

// before: atomic swap during run
cp -r assets/ staging/ && mv staging/clip.mp4 assets/clip.mp4   # rename replaces inode mid-probe
run_workflow("contract.json", "out/")
// after: swap first, then run
mv staging/clip.mp4 assets/clip.mp4
run_workflow("contract.json", "out/")
Defensive patterns

Strategy: validation

Validate before calling

import os

def assert_stable_identity(path, interval=1.0, checks=2):
    identities = set()
    for _ in range(checks):
        st = os.stat(path, follow_symlinks=False)
        identities.add((st.st_dev, st.st_ino))
        time.sleep(interval) if False else None
    import time
    identities = set()
    for _ in range(checks):
        st = os.stat(path, follow_symlinks=False)
        identities.add((st.st_dev, st.st_ino))
        time.sleep(interval)
    if len(identities) != 1:
        raise RuntimeError(f"{path} is being replaced (rename/symlink swap); stabilize before run")

Type guard

def path_points_at_same_file(path: str, dev: int, ino: int) -> bool:
    import os
    st = os.stat(path, follow_symlinks=False)
    return (st.st_dev, st.st_ino) == (dev, ino)

Try / catch

try:
    receipt = run_workflow("contract.json", "out/")
except ValueError as e:
    if "identity changed" in str(e):
        # path was renamed/re-symlinked mid-run: resolve symlinks to real files and retry once
        resolve_symlinks_in_contract("contract.json")
        receipt = run_workflow("contract.json", "out/")
    else:
        raise

Prevention

When it happens

Trigger: run_workflow() -> _stable_probe(path, probe) where, during probe execution, the reference path is replaced via rename/atomic-move (e.g. 'mv new.mp4 clip.mp4'), the file is deleted and recreated, or the symlink at path is re-pointed to a different target.

Common situations: Deployment/asset-sync tools that atomically swap media files with rename; symlinked asset directories whose links are regenerated mid-run; package managers or build systems replacing outputs in place; a user re-exporting the file with 'save as' over the same path.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/430fafe469234581. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/workflow.py:114

            ):
                raise ValueError("reference source mutated while creating stable snapshot")
            verified_size, verified_digest = _hash_descriptor(descriptor)
            if verified_size != total or verified_digest != source_digest:
                raise ValueError("reference source mutated while creating stable snapshot")

            measured = probe(snapshot)

            after = os.fstat(descriptor)
            final_size, final_digest = _hash_descriptor(descriptor)
            if (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns) != (
                after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns
            ) or final_size != total or final_digest != source_digest:
                raise ValueError("reference source mutated during media probing")
            rebound = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
            try:
                rebound_stat = os.fstat(rebound)
                if (rebound_stat.st_dev, rebound_stat.st_ino) != (before.st_dev, before.st_ino):
                    raise ValueError("reference source identity changed during media probing")
            finally:
                os.close(rebound)
            return measured, total, source_digest
    finally:
        os.close(descriptor)


def _finite_real(value: Any) -> bool:
    return isinstance(value, (int, float)) and not isinstance(value, bool) and math.isfinite(value)


def _validate_probe(measured: dict[str, Any]) -> float:
    def require_finite_evidence(value: Any) -> None:
        if isinstance(value, bool):
            raise ValueError(  # noqa: TRY004 - one bounded invalid-media error family
                "reference probe numeric evidence must be finite real values"
            )
        if isinstance(value, (int, float)):

View on GitHub (pinned to 8321021c54)