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
- Re-capture the evidence digest immediately before apply: `expected_digest = candidate_overlay_payload(overlay)[0]`, then call apply.
- Confirm the overlay path is the exact file the evidence was recorded from (same inode, no regeneration in between).
- Pin line endings and serialization (write the overlay once, treat it as immutable, hash it, and reference it by hash).
- 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
- Treat the overlay file as immutable from digest capture through apply.
- Generate the overlay, hash it, and pass the file path + hash together — never a hash captured at a different time.
- Disable any formatter/normalizer that may rewrite the overlay between capture and apply.
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
- overlay destination base binding mismatch:
- frozen overlay bytes do not match the authorized input
- overlay destination parent changed during {phase}: {item['ta
- overlay destination drifted before apply: {replacement['targ
- overlay destination drifted during apply: {replacement['targ
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/4a3ea7597346bd69.
Report an issue: GitHub.