fix(validator): failed re-evaluation inherits the previous run's score from a stale results.json (#249)#250
Conversation
…previous score `evaluate_submission` writes every run of a submission into a workspace keyed by submission id. Submission rows are reused rather than recreated — pushing a new commit to a submission PR updates the existing row — so re-evaluations land in the same directory, and nothing removed the previous `results.json`. A re-run that produced no results therefore did not trip either `rc != 0 and not results_path.exists()` guard, and `_prepare_results` parsed the stale payload: the run was marked `completed` and carried the earlier run's score (and the earlier run's cost ledger). That score propagates to `submission.latest_score`, `best_eval_id`, the leaderboard, `update_king_score` and the worker's merge/close decision, so a broken checkpoint could inherit a previously banked passing score. Unlink `results.json` and `cost_ledger.jsonl` before the run starts, and `rm -f` the remote `results.json` in the remote command prologue — `_remote_attempt` rsyncs the remote workspace back without `--delete`, so a stale remote copy would otherwise overwrite the freshly cleared local one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Two CI notes for reviewers:
What I verified locally, since the validator job can't run on my machine (it needs POSIX
Happy to adjust if you would rather keep the remote-side |
|
CI is green on this branch. I enabled Actions on my fork and ran this repo's unmodified
Run: https://github.com/kai392/minirouter/actions/runs/30106379606 I also ran the state after both this PR and #252 are merged (JOURNAL conflict resolved by keeping both entries) — all three jobs green: https://github.com/kai392/minirouter/actions/runs/30106669053 So the only red mark left here, |
Fixes #249
Root cause
evaluate_submission()writes every run of a submission into a workspace keyed by submission id (data/workspaces/submissions/<submission_id>/). Submission rows are reused, not recreated —create_pr_submission()updates the existing row when a miner pushes a new commit to the same PR — so every re-evaluation lands in the same directory, and nothing removed the previousresults.json.A re-run that produced no results therefore never tripped either guard:
_is_missing_results_payload()(added for #12) never fired either, because the results were not missing — they were from a different run. The run was markedcompletedand carried the earlier run's score, plus the earlier run'scost_ledger.jsonl.The remote path had the same hole one level up:
_remote_attempt()rsyncs the remote workspace back without--delete, and the remote workspace is also keyed by submission id, so a stale remoteresults.jsonwould be copied over a freshly cleared local one.Fix approach
Minimal, two changes in
eval_runner.py:_clear_previous_run_files()unlinksresults.jsonandcost_ledger.jsonlat the top ofevaluate_submission(), before any attempt runs.rm -fs the remoteresults.json, right next to the ledger truncation it already did.No behaviour is changed for a run that actually produces results; the two existing "missing results" guards simply now see the truth.
Impact
The stale score did not stay in the run row — it propagated to
submission.latest_score,best_eval_id, the public leaderboard,update_king_score()and the worker'sshould_promote_submission()merge/close decision. A miner could bank one passing score and have any later broken checkpoint inherit it. The fix also stops broken evaluations from being reported as clean passes.Tests
test_reevaluation_does_not_reuse_previous_results— evaluate once with a scoring stub (0.91), then again with a stub that writes nothing and exits non-zero; the second run must befailedwithscore is None,latest_score is Noneandcost_usd == 0.0. Fails onmain, passes here.test_remote_command_clears_stale_remote_results— the remote prologue removes the remoteresults.json.Risk / tradeoffs
OSErrorfromPath.unlink()on an unwritable workspace, which is deliberately not swallowed: if freshness cannot be guaranteed the evaluation must not proceed, and the worker already marks such a job failed.: > $TRINITY_COST_LEDGER) before running; this only covers the case where the command never starts.EvaluationRun.id, so their workspace is never reused; left untouched.docs/JOURNAL.mdentry added as required by AGENTS.md §6.🤖 Generated with Claude Code