abhigyanpatwari/GitNexus · critical · RuntimeError
candidate overlay changed during the benchmark run
Error message
candidate overlay changed during the benchmark run
What it means
Raised after apply_candidate_overlay() returns a digest that does not match the overlay_digest computed once at program start (runner.py:959). The overlay digest is a content hash of the candidate skill overlay directory captured before any arm runs; the harness re-derives it after applying the overlay into the worktree to detect that the overlay's bytes drifted mid-run. This is a tamper-evidence/integrity guard: a mismatch means the candidate skill surface the model actually saw differs from the surface that was approved at startup.
Source
Thrown at eval/workflow_bench/runner.py:1153
if not setup.ok:
raise ManagedProcessError(setup_command, setup)
# Tamper-evidence: setup must not have rewritten the base
# skills, verified before any candidate overlay lands.
require_skill_fingerprint(
worktree,
execution_arm,
base_skill_digest,
phase="task setup",
)
if arm in CANDIDATE_ARMS:
assert candidate_overlay is not None
applied_digest = apply_candidate_overlay(
candidate_overlay,
worktree,
sandbox=sandbox,
)
if applied_digest != overlay_digest:
raise RuntimeError("candidate overlay changed during the benchmark run")
# The digest the model must preserve during its run is the
# post-overlay skill surface (candidate skills for
# candidate arms; unchanged base skills otherwise).
expected_skill_digest = skill_fingerprint(worktree, execution_arm)
orig_sha = _sandbox_git(sandbox, ["rev-parse", "HEAD"]).strip()
if not re.fullmatch(r"[0-9a-fA-F]{40,64}", orig_sha):
raise RuntimeError("sandboxed candidate setup did not produce an immutable commit")
before_work_digest = (
implementation_diff_digest(sandbox, orig_sha)
if execution_arm in IMPLEMENTATION_ARMS
else ""
)
record = run_arm(
execution_arm,
task,
worktree,
args,
sandbox=sandbox,View on GitHub (pinned to d540b00184)
Solutions
- Stop any concurrent writer to the --candidate-overlay path and re-run the benchmark from a clean overlay snapshot copied to a stable local directory.
- Verify candidate_overlay_digest() is deterministic for your overlay by computing it twice before launching the run; remove non-reproducible files (swap files, .DS_Store, editor backups) from the overlay tree.
- Pin the overlay to a read-only copy (chmod -R a-w or a git commit SHA) for the duration of the run so nothing can mutate it.
Example fix
# before: pass a live, mutable overlay dir runner.py --candidate-overlay /tmp/overlay-wip --arms candidate_workflow workflow # after: snapshot the overlay to a frozen copy first cp -a /tmp/overlay-wip /tmp/overlay-frozen && chmod -R a-w /tmp/overlay-frozen runner.py --candidate-overlay /tmp/overlay-frozen --arms candidate_workflow workflow
Defensive patterns
Strategy: validation
Validate before calling
from eval.workflow_bench.evolution import candidate_overlay_digest
# Compute the overlay digest twice before launching the benchmark;
# any difference means a concurrent writer is active.
d1 = candidate_overlay_digest(overlay_path)
import time; time.sleep(0.1)
d2 = candidate_overlay_digest(overlay_path)
assert d1 == d2, f"overlay is being mutated mid-read: {d1} vs {d2}"
# Freeze the overlay so no further mutation is possible
import shutil, os
frozen = "/tmp/overlay-frozen"
shutil.copytree(overlay_path, frozen, dirs_exist_ok=True)
for root, dirs, files in os.walk(frozen):
os.chmod(root, 0o555)
for f in files:
os.chmod(os.path.join(root, f), 0o444) Prevention
- Never point --candidate-overlay at a directory a live editor or sync client can write to.
- Copy the overlay to a read-only location and pass that path to the harness.
- Run a single benchmark instance per overlay directory; serialize concurrent runs.
When it happens
Trigger: The harness calls candidate_overlay_digest(candidate_overlay) at startup (runner.py:959) and apply_candidate_overlay(candidate_overlay, worktree, sandbox=sandbox) at runner.py:1147 during a candidate arm run; if the returned applied_digest != overlay_digest the RuntimeError fires. Concretely: a background process or second benchmark instance writes to the --candidate-overlay directory between those two calls, the overlay directory contains a symlink whose target changes, or candidate_overlay_payload() is non-deterministic (e.g. it picks up editor swap files or .DS_Store that vary).
Common situations: Running two evolve.py/runner.py processes pointed at the same --candidate-overlay scratch dir; an editor auto-saving into the overlay tree; the overlay living in a synced (Dropbox/icloud) folder that mutates during the run; regenerating the overlay mid-benchmark.
Related errors
- oracle source changed while being captured: {relative}
- oracle stage root changed during verification
- oracle stage parent changed during verification: {item.targe
- oracle file changed during verification: {item.target}
- hidden oracle mountpoint changed type during verification
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/4c4b6b4765db4927.
Report an issue: GitHub.