abhigyanpatwari/GitNexus · error · ValueError

candidate overlay digest no longer matches promotion evidenc

Error message

candidate overlay digest no longer matches promotion evidence

What it means

Thrown at the top of `apply_promoted_overlay` when the caller passed `expected_digest` and the recomputed SHA-256 of the candidate overlay payload no longer matches it. The overlay file (its canonical bytes) changed between evidence capture and apply, so the promotion evidence is stale.

Source

Thrown at eval/workflow_bench/promotion_apply.py:626

            handle.flush()
            os.fsync(handle.fileno())
    finally:
        os.close(descriptor)
    os.fsync(root_descriptor)
    return root_path / recovery_name


def apply_promoted_overlay(
    overlay: Path,
    repo_root: Path = REPO_ROOT,
    *,
    expected_digest: str | None = None,
    expected_target_bases: dict[str, str] | None = None,
) -> list[str]:
    """Compare-and-swap one evidence-bound overlay across every mirror."""
    digest, payload = candidate_overlay_payload(overlay)
    if expected_digest is not None and digest != expected_digest:
        raise ValueError("candidate overlay digest no longer matches promotion evidence")

    repo_root, root_descriptor, prepared = _prepare_targets(payload, repo_root)
    current_bases = {item["target"].as_posix(): item["base_digest"] for item in prepared}
    if expected_target_bases is not None and expected_target_bases != current_bases:
        expected_paths = set(expected_target_bases)
        current_paths = set(current_bases)
        missing = sorted(current_paths - expected_paths)
        unexpected = sorted(expected_paths - current_paths)
        drifted = sorted(
            path for path in current_paths & expected_paths if current_bases[path] != expected_target_bases[path]
        )
        details = []
        if missing:
            details.append("missing=" + ",".join(missing))
        if unexpected:
            details.append("unexpected=" + ",".join(unexpected))
        if drifted:
            details.append("drifted=" + ",".join(drifted))

View on GitHub (pinned to d540b00184)

Solutions

  1. Re-capture the evidence digest immediately before apply: `expected_digest = candidate_overlay_payload(overlay)[0]`, then call apply.
  2. Confirm the overlay path is the exact file the evidence was recorded from (same inode, no regeneration in between).
  3. Pin line endings and serialization (write the overlay once, treat it as immutable, hash it, and reference it by hash).
  4. If the overlay legitimately changed, regenerate `expected_digest` and `expected_target_bases` together — never partially.

Example fix

# before: stale digest from an earlier build
apply_promoted_overlay(overlay, expected_digest='abc123...')  # -> ValueError
# after: capture the digest of the exact file you are about to apply
from workflow_bench.promotion_apply import candidate_overlay_payload
digest, _ = candidate_overlay_payload(overlay)
apply_promoted_overlay(overlay, expected_digest=digest)
Defensive patterns

Strategy: validation

Validate before calling

from workflow_bench.promotion_apply import candidate_overlay_payload

def capture_fresh_digest(overlay) -> str:
    """Capture the digest of the exact bytes you are about to apply."""
    return candidate_overlay_payload(overlay)[0]

# use it immediately
digest = capture_fresh_digest(overlay)
apply_promoted_overlay(overlay, expected_digest=digest)

Type guard

null

Try / catch

try:
    apply_promoted_overlay(overlay, expected_digest=expected)
except ValueError as exc:
    if "digest no longer matches" in str(exc):
        # overlay changed: re-capture digest AND target bases together, then retry
        from workflow_bench.promotion_apply import destination_base_digests
        expected = candidate_overlay_payload(overlay)[0]
        bases = destination_base_digests(overlay)
    raise

Prevention

When it happens

Trigger: Calling `apply_promoted_overlay(overlay, expected_digest=<d>)` after the overlay file was edited, regenerated, or re-serialized, so `candidate_overlay_payload(overlay)[0]` returns a different hash than the `expected_digest` recorded earlier.

Common situations: A build step rewrote the overlay between evidence capture and apply; two overlays share a path and the wrong one was passed; line-ending normalization changed the bytes; a formatter touched the file; the digest was captured from a different branch.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12). Data as JSON: /api/errors/4a3ea7597346bd69. Report an issue: GitHub.