Skip to content

issue-triage evals: add opencode harness, fix two broken assertions, harden the skill - #326

Open
sibson wants to merge 5 commits into
mainfrom
evals-opencode-harness-and-fixes
Open

sibson wants to merge 5 commits into
mainfrom
evals-opencode-harness-and-fixes

Conversation

@sibson

@sibson sibson commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds an opencode-driven eval harness (worktree-per-run isolation, permission deny-list, trace distillation for the no-writes screen) so the issue-triage skill can be measured against non-Anthropic models — used to benchmark opencode/big-pickle.
  • Runs the first real skill-vs-no-skill comparison this suite has had. Every prior recorded run was with-skill only, so nothing before this could say whether the skill helps at all.
  • Regrades a subset with skill-creator's own grader (the harness these evals were written for but weren't actually being run through) and finds two broken assertions as a result:
    • Case 3's assertion demanded noting a celery/python version floor that fixtures/issue-270.json never states — failed by 8 of 8 runs across three models and both arms.
    • Case 1's label assertion was negative-only, so a run proposing no labels scored identically to one proposing the correct ones.
  • Re-runs cases 1 and 3 with real baseline isolation (SKILL.md physically removed from the baseline's checkout, not just omitted from the prompt) after discovering the first isolation-free attempt let the "without skill" arm read the skill straight off disk.
  • Fixes three defects in SKILL.md that the eval runs surfaced directly:
    • A run called an issue "probably fixed" on the strength of an unmerged branch — now requires confirming the fix commit is an ancestor of main.
    • A dry-run draft comment claimed a regression test had been added (with a literal #PPP placeholder) — now states explicitly that dry-run drafts describe work actually done, never narrate actions that weren't taken.
    • The duplicate-search example named the exact issue number (#210) that's also this suite's own eval fixture, handing a with-skill run the answer to its own grading. Genericised.

Why

The skill's actual measured benefit over a bare model was much smaller than the first pass suggested (see evals/results/2026-07-27-isolated-cases-1-3.json) — a meaningful chunk of the original delta came from a broken assertion and from a reference file that transcribes the answer to its own eval case. Chasing that down surfaced real bugs in the skill itself, which are the more valuable fix here.

What a reviewer should know

  • evals/results/*.json are point-in-time measurement records, not code — several were produced, then partially superseded by later ones in the same session as isolation/rubric bugs were found. 2026-07-27-isolated-cases-1-3.json and the SKILL.md fixes are the parts that matter going forward; the intermediate records are kept for the paper trail (see each file's own caveats).
  • No production code changed — this is entirely .claude/skills/issue-triage/ (the skill definition, its eval harness, and eval result records).
  • evals/opencode/README.md documents the new harness and its known gaps (unblinded grading, no repeated sampling, self-reported vs trace-verified execution).

sibson and others added 5 commits July 27, 2026 14:48
Runs the six issue-triage cases against any model opencode can reach, with a
with_skill/without_skill arm each. The prompts differ from evals.json out of
necessity -- opencode resolves slash commands from .opencode/command/ rather
than from skills, so /issue-triage becomes a skill-tool instruction -- which
makes these results non-comparable to the Agent-tool records without reading
the harness_deltas field first.

Fixture paths are absolute because relative ones are dangerous here: a baseline
run resolved evals/fixtures/issue-307.json against $HOME, found nothing, and
webfetched the live issue instead, grading against today's thread rather than
the frozen report with no sign in the output. webfetch is now denied and the
runner refuses to start a run naming a path that does not exist.

check_no_writes.py needs a distilled tool-call log rather than the transcript:
opencode's skill tool inlines SKILL.md, which contains 'issue_write' in the
sentence forbidding it, so every with-skill run failed on the skill's own
prohibition.

big-pickle at variant=high scored with_skill 29/42, without_skill 25/42. The
totals understate what separates them (evidence quality, and never drafting a
close) and hide two inversions where the baseline scored higher.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every prior record in this directory ran with_skill only, so none of them could
answer whether the skill helps -- they compared models to each other. This adds
the missing baseline arm: 6 cases x {opus-5 medium, sonnet-5 high} x {with, without}.

Effort is set per agent rather than inherited, which is a first for these
records; both earlier files carry session-default effort.

Result: opus 38/42 with vs 33/42 without, sonnet 33/42 with vs 34/42 without.
The whole delta is case 1 (12 -> 19 summed across models); cases 2-6 move 46 ->
45. The skill supplies procedure, not reasoning -- bare Opus analysed the due_at
bug better than the assertion list can measure, then skipped the duplicate
search, the regression test and the labels entirely.

Cases 4, 5 and 6 now score identically across all four Claude cells. By the
standard in results/README.md they have stopped discriminating at this tier.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
skill-creator is the off-the-shelf harness these evals were written for -- same
evals.json schema, same with_skill/without_skill workspace layout, and it ships
aggregation with variance, a grader, and an analyzer that looks for exactly the
non-discriminating assertions we found by hand.

Regrading the 12 opus runs with its grader.md agrees with the hand grading in
10 of 12 cells; both disagreements are +1 and land one per arm, so the skill
delta is +5 either way. The scores stand.

Its value is grader.md steps 4 and 6, which the hand grading skipped. Two
rubric defects fell out:

- Case 3's version-floor assertion is unsatisfiable. fixtures/issue-270.json
  contains no version information at all, and 8 of 8 runs across three models
  and both arms have failed it. That is a broken assertion, not a model
  weakness.

- Case 1 is partly teaching to the test. references/repro-harness.md works
  through issue 307 by name, including the RedBeatCase subclass, the
  expectedFailure marker and the tests/test_entry.py destination -- three of
  the ten assertions. Case 1 is also the only case where the skill measurably
  helps on Claude models, so part of that gain is transcription from a file
  the baseline never sees.

Step 4 claim-checking also caught a wrong sha, a missed commit, a line ref off
by six and an overstated reproduction claim inside a run that scored 10/10.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Case 3's first assertion demanded that the run note the reported environment
predates the supported celery/python floor. fixtures/issue-270.json states no
version at all -- the only temporal signal is the 2024-02-02 filing date, which
is already above setup.cfg's floor. It had been failed by 8 of 8 runs across
three models and both arms. Rewritten around what the fixture actually
supports; it passed for the first time on the with_skill re-run.

Case 1's label assertion was negative-only, so a run proposing no labels scored
the same as one proposing the right ones. Rewritten as positive plus guard; the
isolated baseline now correctly fails it for proposing 'good first issue'.

Case 1 also gains an assertion_kinds field marking three assertions
transcribable: references/repro-harness.md works through issue 307 by name, so
the RedBeatCase subclass, the expectedFailure marker and the tests/test_entry.py
destination are copyable by any run that reads the skill. The isolated re-run
confirms the tag is accurate -- 3 of 3 pass with the skill, 0 of 3 without, and
they do not leak from the repo's own test suite.

Isolated results, opus medium: case 1 10/10 vs 4/10, case 3 6/6 vs 5/6.
Excluding the transcribable group, case 1 is 7/7 vs 4/7.

This is the first record here whose baseline was actually isolated. A first
attempt without worktrees showed why it matters: both baselines read SKILL.md
and the references off disk, and the two arms collided on the same scratch
filename in the shared tree. Prompt omission is not enforcement.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- big-pickle called #307 probably-fixed on the strength of an unmerged branch,
  which would have told the reporter their live bug was handled. Add an
  explicit rule: a fix has to be an ancestor of main before it counts, an
  unmerged fix is reported as such, not as fixed.
- one run's dry-run draft comment read 'I've added a failing regression test
  in #PPP' -- a placeholder claiming an action that was never taken. State
  the tense explicitly: a dry-run draft describes work actually done, never
  narrates a comment/branch/PR that wasn't created.
- the duplicate-search example named issue #210 by number, which is also this
  skill's own eval fixture -- a with_skill run got the answer read straight
  out of the guidance meant to teach it how to search. Genericised the
  example so it teaches the technique without pre-solving the case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant