abhigyanpatwari/GitNexus · error · ValueError

overlay destination base binding mismatch:

Error message

overlay destination base binding mismatch: 

What it means

Thrown in `apply_promoted_overlay` when the caller passed `expected_target_bases` and the on-disk base digests differ from what was expected. The message enumerates which paths are missing, unexpected, or drifted, so you can see exactly how the working tree diverged from the promotion evidence.

Source

Thrown at eval/workflow_bench/promotion_apply.py:646

    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))
        _close_prepared(root_descriptor, prepared)
        raise ValueError("overlay destination base binding mismatch: " + "; ".join(details))

    replacements: list[dict[str, Any]] = []
    completed: list[dict[str, Any]] = []
    preserve_backups = False
    published_all = False
    rollback_complete = False

    def entry_state(replacement: dict[str, Any], name: str) -> tuple[str, int]:
        current, mode = _read_destination_at(
            replacement["parent_descriptor"],
            name,
            target=replacement["target"],
        )
        return hashlib.sha256(current).hexdigest(), stat.S_IMODE(mode)

    def current_state(replacement: dict[str, Any]) -> tuple[str, int]:
        return entry_state(replacement, replacement["name"])

View on GitHub (pinned to d540b00184)

Solutions

  1. Read the message: `drifted=` paths need re-capture, `missing=` paths are new on disk, `unexpected=` paths are gone — handle each category.
  2. Re-capture `expected_target_bases` with `destination_base_digests(overlay)` immediately before apply and pass the fresh dict.
  3. Restore the worktree to the state evidence was captured from (e.g. `git checkout -- .` or `git restore --source=<ref>`).
  4. Ensure no concurrent writer (formatter, IDE, build) touches the repo between capture and apply.

Example fix

# before: stale expected_target_bases from an earlier capture
apply_promoted_overlay(overlay, expected_target_bases=stale_bases)  # -> ValueError
# after: re-capture fresh and apply atomically
from workflow_bench.promotion_apply import destination_base_digests
fresh = destination_base_digests(overlay)
apply_promoted_overlay(overlay, expected_target_bases=fresh)
Defensive patterns

Strategy: validation

Validate before calling

from workflow_bench.promotion_apply import destination_base_digests

def current_target_bases_match(overlay, expected: dict[str, str]) -> bool:
    return destination_base_digests(overlay) == expected

# capture right before apply
bases = destination_base_digests(overlay)
assert current_target_bases_match(overlay, bases)
apply_promoted_overlay(overlay, expected_target_bases=bases)

Type guard

null

Try / catch

try:
    apply_promoted_overlay(overlay, expected_target_bases=expected)
except ValueError as exc:
    if "base binding mismatch" in str(exc):
        # parse drifted=/missing=/unexpected= and re-capture
        bases = destination_base_digests(overlay)
    raise

Prevention

When it happens

Trigger: Calling `apply_promoted_overlay(overlay, expected_target_bases=<dict>)` after the destination files changed on disk (paths edited, added, removed, or their mirror targets changed). The diff of expected vs. current base digests is non-empty and the detailed `missing=`/`unexpected=`/`drifted=` breakdown is appended.

Common situations: A file was edited in the worktree after `destination_base_digests` was captured; a new file was added to the tree; a file was deleted; the overlay's `mirror_targets` mapping changed so the target set no longer matches; running apply against a different branch than the one evidence was captured on.

Related errors


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