Skip to content

fix(judge): {"match": "false"} is scored as a PASS — parse verdicts as tri-state (closes #295) - #305

Merged
Perry2004 merged 1 commit into
mainfrom
fix/judge-verdict-parsing
Aug 19, 2026
Merged

fix(judge): {"match": "false"} is scored as a PASS — parse verdicts as tri-state (closes #295)#305
Perry2004 merged 1 commit into
mainfrom
fix/judge-verdict-parsing

Conversation

@reacher-z

Copy link
Copy Markdown
Collaborator

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():

# judge.py:187 (strict rubric)
return bool(obj.get("match")), str(obj.get("reason", ""))
# judge_llm.py:156 (lenient rubric — produces the published Reward-lenient column)
return bool(obj.get("match", True)), str(obj.get("reason", ""))

bool("false") is True. A judge replying {"match": "false"} — a string, which models emit routinely — had its mismatch recorded as a PASS. Reproduced on main before the fix:

judge reply judge.py judge_llm.py
{"match": "false", "reason": "wrong item"} True True
{"match": "FALSE"} True True
{"reason": "forgot the verdict"} False True
{"match": false} False ✓ False ✓

judge_llm.py was worse on two counts: a missing match key defaulted to True, and its keyword fallback returned True for 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 — to None.

None is not a new state: judge.py:248/267 already returns it for call failures, and run.py gates on judge_result.get("match") is True (:662, :746), so an inconclusive verdict is not a pass. The lenient keyword fallback now returns None instead 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-run clawbench-rescore for 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.

…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 Perry2004 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@Perry2004
Perry2004 merged commit 7f07288 into main Aug 19, 2026
5 checks passed
@Perry2004
Perry2004 deleted the fix/judge-verdict-parsing branch August 19, 2026 03:16
@github-project-automation github-project-automation Bot moved this from Todo to Done in ClawBench Aug 19, 2026
@Perry2004 Perry2004 added the bug Something isn't working label Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

judge: {"match": "false"} is scored as PASS (bool("false") is True), and judge_llm defaults a missing verdict to pass

2 participants