fix(judge): {"match": "false"} is scored as a PASS — parse verdicts as tri-state (closes #295) - #305
Merged
Merged
Conversation
…s scoring as a pass (closes #295) Both judges read the verdict with bool(), and bool("false") is True — so a model replying with the *string* "false" (a routine LLM quirk) had its mismatch recorded as a PASS. judge_llm.py additionally defaulted a missing verdict key to True, and its keyword fallback returned True for any reply it could not parse; that module produces the published Reward-lenient column. _coerce_match() now maps real booleans and the common string spellings to a verdict and everything else to None, which run.py already treats as inconclusive (it gates on `match is True`). The lenient fallback no longer invents a pass for unparseable replies. Adds a parse matrix for both modules: booleans, string spellings, casing, fenced JSON, missing key, null, non-verdict junk.
Perry2004
approved these changes
Aug 19, 2026
Perry2004
left a comment
Collaborator
There was a problem hiding this comment.
I have checked that the bug only happens when the judge LLM returns a malformed result (returning falsy strings/texts instead of valid boolean). Therefore it wont influence the validity of existing judges.
The fix LGTM. Will merge and include in a patch release.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #295. Scoring-correctness fix — it changes numbers, so it deserves a careful read.
The bug
Both judges turned the model's verdict into a boolean with
bool():bool("false")isTrue. A judge replying{"match": "false"}— a string, which models emit routinely — had its mismatch recorded as a PASS. Reproduced onmainbefore the fix:judge.pyjudge_llm.py{"match": "false", "reason": "wrong item"}{"match": "FALSE"}{"reason": "forgot the verdict"}{"match": false}judge_llm.pywas worse on two counts: a missingmatchkey defaulted toTrue, and its keyword fallback returnedTruefor any reply it could not parse. Both inflate the lenient column specifically.The fix
_coerce_match()maps real booleans and the common string spellings ("true"/"false"/"yes"/"no"/"pass"/"fail"/"match"/"mismatch", case-insensitive) to a verdict, and everything else — missing key,null,"maybe", junk — toNone.Noneis not a new state:judge.py:248/267already returns it for call failures, andrun.pygates onjudge_result.get("match") is True(:662,:746), so an inconclusive verdict is not a pass. The lenient keyword fallback now returnsNoneinstead of inventing a pass.After the fix, every row above resolves correctly (
"false"/"FALSE"→False, missing key →None).Tests
New parse matrix over both modules — booleans, string spellings, casing, fenced JSON, missing key,
null, non-verdict junk, and a type-rejection case for_coerce_match. Full suite: 196 passed; ruff and pyright clean.What this means for published numbers
Any run whose judge answered with a stringly-typed mismatch was scored as a pass. I have not quantified how many rows that affects — it depends on how often each judge model stringifies, which is visible in the stored raw verdicts. Suggested follow-up: grep the archived judge outputs for
"match": "and, if there are hits, re-runclawbench-rescorefor the affected rows before the next leaderboard refresh. Happy to do that as a separate PR.@Perry2004 — please review carefully: this changes what counts as a pass. The two calls I'd most like a second opinion on are (1) treating a missing verdict key as inconclusive rather than a mismatch, and (2) the accepted string vocabulary — I kept it deliberately narrow.