◉CLAUDE LABJP
●2.1.289 — Claude Code 2.1.289 (Oct 3) fixes deny/ask rules on nested parts of compound shell commands, a terminal freeze, and Read deny through symlinks●10/07 — 1 day left until the old spellings of Claude Desktop / Cowork managed-config keys stop being accepted. After the cutoff they fail closed●ACCOUNTS — A request to switch between multiple accounts in Claude Desktop has gathered over 200 comments on GitHub. How far today's workarounds go is the real question●NEW — When Claude Code committed despite a no-commit instruction, I separated the cause with a deny rule and a PreToolUse hook●COMPARE — A Zenn post compares Opus 5.5 and Sonnet 5.5 with numbers. The real question is where to draw the line per job●SONNET5.5 — Code written for Sonnet 5 can break on 5.5 in five ways. Thinking settings and forced tool_choice are the places to check first●2.1.289 — Claude Code 2.1.289 (Oct 3) fixes deny/ask rules on nested parts of compound shell commands, a terminal freeze, and Read deny through symlinks●10/07 — 1 day left until the old spellings of Claude Desktop / Cowork managed-config keys stop being accepted. After the cutoff they fail closed●ACCOUNTS — A request to switch between multiple accounts in Claude Desktop has gathered over 200 comments on GitHub. How far today's workarounds go is the real question●NEW — When Claude Code committed despite a no-commit instruction, I separated the cause with a deny rule and a PreToolUse hook●COMPARE — A Zenn post compares Opus 5.5 and Sonnet 5.5 with numbers. The real question is where to draw the line per job●SONNET5.5 — Code written for Sonnet 5 can break on 5.5 in five ways. Thinking settings and forced tool_choice are the places to check first
Articles/API & SDK
⬡ API & SDK/2026-05-30Advanced

Catching Claude Quality Regressions With an Eval Harness

I tweaked a prompt by one line and, for a different set of inputs, the output quietly got worse. Here is the eval harness I built to protect Claude's production quality across every prompt change and model update, with full implementation code and real operating numbers.

Claude49evalqualityLLM-as-judgeAPI28

✦ Premium Article

I tweaked a prompt, and something else quietly broke

I once changed a classification prompt by a single line — "let's have it classify a little more carefully" — and shipped it. The handful of inputs I tried locally were clearly better. A few days later a report came in: one specific category had gotten worse. The inputs I had checked improved; the inputs I hadn't checked quietly degraded.

I've been building apps on my own since 2014, reaching somewhere around 50 million downloads over the years. With code, tests guard against a change breaking existing behavior. But in a feature built on Claude, the same prompt produces different output depending on the input, so traditional assertion-style tests don't carry over as-is. "Tried a few, looks good, ship it" was a bet placed after seeing only a sliver of the input distribution.

So I built an eval harness that mechanically detects quality regressions every time I change a prompt or a model. My grandfather, a temple carpenter, taught me that working with your hands is a kind of devotion — and an eval foundation is exactly the kind of quiet, hands-on groundwork that pays off later. Here is the implementation, and the numbers I saw running it.

Why you need evals, not unit tests

Claude's output is not deterministic. Even at temperature 0, slightly different input yields different output, and even a minor model update shifts behavior. What you want to protect is not "a specific string for a specific input" but "a quality level across the whole input distribution."

That is the decisive difference from conventional testing. A unit test pins down a single point; an eval measures a distribution. The metrics worth guarding boil down to two: first, the average quality score over a representative set of inputs; second, its spread — especially the deterioration of the lowest-scoring band. The opening incident would have slipped past a pure average. Degradation on a subset like one category only surfaces when you look at stratified scores.

Building a golden dataset, minimally

The foundation of an eval is a golden dataset. You don't need perfect expected outputs. It's far more practical to attach a "pass condition" — a rubric — to each input. I made just 40 cases in the first week, then added one each time an incident or complaint appeared, growing it to 200 over six months.

# golden.jsonl — one case per line. "expected" is a rubric, not a string match.
# {"id": "cat-012", "category": "refund", "input": "...", "rubric": ["classified as refund", "states a one-sentence reason", "no PII in output"]}
 
import json
from pathlib import Path
 
def load_golden(path: str) -> list[dict]:
    cases = []
    for line in Path(path).read_text(encoding="utf-8").splitlines():
        line = line.strip()
        if line:
            cases.append(json.loads(line))
    return cases

The key is to break each rubric into items you can judge objectively as YES/NO. Vague criteria like "should be polite" produce wobbly judgments. Driving items down to a grain where humans wouldn't disagree — "states a reason in one sentence," "uses no category name outside the allowed list" — was the single biggest lever for the judge-agreement rate below.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦A complete eval harness that catches quietly-degraded output from prompt or model changes before you push
✦Rubric design that tamed LLM-as-judge drift and lifted agreement with human grading from 68% to 91%
✦How to set regression thresholds for CI, plus the real per-run cost of my 200-case golden set
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $15 for lifetime access
View Membership →

Related Articles

⬡ API & SDK2026-07-24
When Memory Store Listings Returned Half the Rows: Migrating to agent-memory-2026-07-22
agent-memory-2026-07-22 changed memories.list: fixed ordering, stricter depth, segment-based path_prefix matching. How an audit quietly halved, an access layer that survives it, finding every call site with ast, and measured depth=1 traversal cost.
⬡ API & SDK2026-03-15
Building an AI Chatbot with Claude API — Streaming, Conversation History & Cost Optimization
A record of building a chatbot on the Claude API from a minimal script upward: conversation history, streaming, a FastAPI endpoint, cost trimming, LINE/Telegram/Discord, and PII masking. The retry logic was rebuilt around measured jitter and the retry-after header.
⬡ API & SDK2026-05-02
Designing a Claude API Monthly Budget That Doesn't Blow Up — Cost Management for Solo Developers
When you embed Claude API into a side-project app, the first thing you hit is the end-of-month invoice. Here are the budgeting frameworks, monitoring patterns, and implementation tricks I use to keep costs predictable — drawn from running my own apps.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links