"Compares two models' scored eval performance on the same case set via claude plugin eval --model, reusing existing graders. Use when choosing between models."
Resources
1Install
npx skillscat add wasikarn/method-harness/model-bench Install via the SkillsCat registry.
Model Bench
Run this repo's eval suite once per subject model and diff the scores. It reuses the existing
graders and judge panel under evals/; it adds only a labeled side-by-side of twoclaude plugin eval runs. Invoked as /mh:model-bench only, because it spends judge-token cost.
Run
bash "${CLAUDE_SKILL_DIR}/scripts/model-bench.sh" <model-a>[@effort] <model-b>[@effort] [-- <claude plugin eval args>]Flags after -- pass through to both runs (--tag, --case, --scaffold, --allow-tools, …).
Unless already passed, the wrapper adds:
--ablation none: the defaultwith-withoutdoubles cost and can compare a disabled-plugin
baseline instead of the models.--threshold 0: avoids the CLI's exit 1 on a normal run (exit 1/2 still proceed to the diff).--max-cost-usd 5per arm; pass your own to override.
It also flips defaultEnabled in .claude-plugin/plugin.json for the run and always restores it:
eval sandboxes load no user settings, so defaultEnabled: false would silently score a
no-plugin fallback.
Diff two finished runs without spending anything:
bash "${CLAUDE_SKILL_DIR}/scripts/model-bench.sh" --diff-only \
--label-a <name> --label-b <name> \
<result-a>/aggregate-result.json <result-b>/aggregate-result.jsonArm syntax: <model>[@effort]
model@effort sets CLAUDE_CODE_EFFORT_LEVEL=<effort> for that arm's eval call only; a bare model
inherits the shell's value (usually unset). The CLI's --effort flag never reaches the eval child,
so the wrapper never uses it. effort is one of low|medium|high|xhigh|max; a bare @, model@,@effort or a second @ is a usage error before any claude call. The full arm string is the
report label, and each arm's output dir gets an arm-meta.txt (arm spec, command,claude --version, timestamp).
Scope caveats
- Agent-dispatch cases. A case whose
prompt.mddispatches a subagent (subagent_type:, or agraders/agent-fired.md) runs that agent at its frontmatter-pinned model regardless of--model, except that a same-family arm moves a family-alias pin (model: opusfollows an Opus
arm). Whether--modelreaches subagent dispatch in the sandbox is unverified. The report
prints how many compared cases are agent-dispatch.CLAUDE_CODE_EFFORT_LEVELoutranks
frontmattereffort:, so an@effortarm should reach dispatched agents too; also unprobed. - Safety-classifier fallback. Arms on Fable, Opus 5.5, Opus 5 or the
opus/fable/best/defaultaliases can be scored on the wrong model: a flagged request re-runs on Opus 4.8 or
Opus 5 (on an Opus 5 arm a biology flag ends in a refusal). Eval runs ignore the user'sswitchModelsOnFlag, andaggregate-result.jsonrecords no model. The report prints a caveat
for such an arm. - How to verify any claim about what a child resolved (model, effort): re-run the case with
--keep-tempand read the child's ownconfig/projects/**/*.jsonltranscript, whose assistant
turns recordmodelandeffort. Never the outer--debug-file, which shows only the parent.
Reading the report
- Hard-fail, no comparison: either side has a
suite.plugins[].problem(plugin didn't load) orpartial: true(a ceiling cut the run short, so its case set differs). - WARN, still compared: judge model, case filter, ablation mode, Claude Code version or plugin
version differ between sides, or an arm's model family matches its own judge's family. Read the
warning before trusting the delta. - Not comparable: cases present on only one side (usually different
--tag/--casefilters).
Never re-derive scores by hand from the JSON. aggregate-result.json never records the model, so
the printed label is the only record: always pass real model names.