An update on recent Claude Code quality reports
At 9:14 a.m., an agent adds a six-line null check. At 9:26, the same agent—using the same model and repository—rewrites three unrelated files, forgets a tool result, and claims tests passed when the command never completed. Developers naturally conclude that “the model got worse.”
That conclusion may be wrong.
Recent Claude Code quality concerns are a useful reminder that an coding agent is not just a model. It is a pipeline containing context assembly, tool execution, result serialization, caching, routing, retries, and context compaction. A defect in any layer can look exactly like degraded reasoning.
The practical response is not to dismiss subjective quality reports, nor to treat every bad session as proof of a model regression. We need instrumentation that can distinguish model behavior from agent-runtime failures.
“Claude quality” is several systems wearing one label
When developers use Claude Code, they experience one product surface. Underneath it, at least six components influence the result:
- Model selection and routing: Which model, snapshot, region, or serving path handled the request?
- Prompt construction: Which system instructions, repository files, and conversation turns reached the model?
- Context management: Was old context retained, truncated, summarized, or compacted?
- Tool execution: Did shell commands and file reads complete, time out, or return partial output?
- Result encoding: Were tool results escaped, reordered, duplicated, or omitted?
- Agent policy: When did the runtime retry, ask permission, or decide the task was finished?
This distinction matters because the symptoms overlap.
| Observed symptom | Possible model cause | Possible runtime cause |
|---|---|---|
| Agent ignores an earlier requirement | Weak instruction retention | Requirement was removed during compaction |
| Agent repeats a file read | Poor planning | Tool result was not attached to the next turn |
| Agent edits unrelated files | Bad task decomposition | Stale repository state or duplicated tool response |
| Agent says tests passed incorrectly | Hallucination | Truncated command output hid the failure |
| Quality varies between identical runs | Sampling sensitivity | Different routing, prompt assembly, or retry path |
| Long sessions degrade sharply | Context saturation | Faulty summarization or token accounting |
In practice, the model is often blamed first because it produces the visible text. The invisible parts of the stack deserve equal suspicion.
What a quality incident actually looks like
A conventional API outage has clean signals: elevated 500 rates, timeouts, or unavailable endpoints. Agent quality failures are harder. Requests still return 200 OK. Latency may look normal. Token counts may remain plausible. The result is simply less useful.
That creates three challenges.
Reports arrive before metrics
A developer notices that Claude Code forgot a constraint or stopped searching too early. No standard infrastructure alarm fires. Ten similar comments can be dismissed as anecdotal, but they may also be the earliest evidence of a real fault.
Subjective feedback is noisy, not worthless. I treat clusters of specific symptoms—lost tool results, premature completion, repeated edits—as leads that should generate reproducible tests.
Multiple faults can create one narrative
Two unrelated issues can occur during the same period. For example, a compaction problem may affect long sessions while a tool-result bug affects commands with large output. Users experience both as “Claude is worse.”
Trying to prove one universal root cause wastes time. Segment reports by:
- Model and model snapshot
- Session length
- Input and output token count
- Tool type
- Tool-output size
- Whether compaction occurred
- Region or serving route
- Client and extension version
- Retry count
The important question is not “Did quality decline?” It is “Which class of requests changed, and at what boundary?”
Recovery is not instantly visible
Even after a runtime defect is fixed, developers may keep using old sessions containing corrupted or poorly summarized context. Client-side caches and pinned versions can extend the perceived incident.
For an agent problem, recovery testing should include both fresh sessions and continued sessions. They exercise different state.
Build a replay harness before you need one
API teams should maintain a small set of real coding tasks that can be replayed against Claude, GPT, and Gemini models. Avoid snapshot-testing exact prose; capable models can produce different valid solutions.
Instead, check observable outcomes. A task might require the agent to:
- Change only
src/discount.py - Preserve the public function signature
- Handle negative values
- Run a specified test command
- Avoid adding dependencies
Here is a deliberately small evaluator:
from pathlib import Path
import subprocess
ALLOWED_FILES = {"src/discount.py"}
def changed_files() -> set[str]:
result = subprocess.run(
["git", "diff", "--name-only"],
check=True,
capture_output=True,
text=True,
)
return {line for line in result.stdout.splitlines() if line}
def evaluate() -> dict:
files = changed_files()
tests = subprocess.run(
["python", "-m", "pytest", "tests/test_discount.py", "-q"],
capture_output=True,
text=True,
timeout=60,
)
source = Path("src/discount.py").read_text()
return {
"tests_pass": tests.returncode == 0,
"scope_ok": files <= ALLOWED_FILES,
"signature_preserved": "def apply_discount(" in source,
"changed_files": sorted(files),
"test_output": tests.stdout[-2000:],
}
if __name__ == "__main__":
print(evaluate())
Run each task in a clean worktree or container:
git worktree add /tmp/agent-eval HEAD
cd /tmp/agent-eval
python run_agent.py --task tasks/discount.md
python evaluate.py
git worktree remove --force /tmp/agent-eval
For investigation, record the request envelope as well as the score:
{
"run_id": "eval-2026-04-23-017",
"provider": "anthropic",
"model": "sonnet-4.6",
"client_version": "1.18.2",
"input_tokens": 48213,
"output_tokens": 3184,
"cache_read_tokens": 40110,
"compaction_count": 1,
"tool_calls": 14,
"largest_tool_result_bytes": 92744,
"tests_pass": true,
"scope_ok": false
}
The exact fields vary by provider and client. The principle does not: preserve enough metadata to separate a weak answer from missing context or a failed tool call. Do not log secrets, raw environment variables, or proprietary source unless your retention policy explicitly permits it.
Test context boundaries, not just prompts
A common gotcha is evaluating only short, single-turn requests. Coding agents fail at state transitions: immediately before compaction, after a large test result, during retries, or when the context approaches its limit.
Suppose a model supports a large context window. That number is a capacity ceiling, not a promise that every token will remain equally influential. An agent may also compact well before the provider’s hard limit to reserve room for output and tools.
Create variants of the same task at meaningful boundaries:
- 8,000-token clean session
- 64,000-token session with relevant history
- 64,000-token session containing noisy build logs
- First turn after context compaction
- Tool response truncated at the client’s maximum size
- Retried tool call with the same idempotency key
Use token counts rather than file counts. One generated lockfile can outweigh dozens of source files.
For large command output, preserve the exit code and both ends of the stream:
def compact_command_result(stdout: str, stderr: str, code: int) -> dict:
limit = 8_000
return {
"exit_code": code,
"stdout_head": stdout[:limit],
"stdout_tail": stdout[-limit:],
"stderr_head": stderr[:limit],
"stderr_tail": stderr[-limit:],
"stdout_truncated": len(stdout) > limit * 2,
"stderr_truncated": len(stderr) > limit * 2,
}
Keeping only the first bytes can hide the final compiler error. Keeping only the last bytes can remove the command’s setup and affected package. Most importantly, never discard the exit code.
Compare providers without hiding failures
Multi-model fallback can reduce incident impact, but it is not automatic reliability. Claude gpt-5.6-sol, Sonnet 4.6, Haiku 4.5, Fable 5 with a 1M context window, GPT-5.5, and Gemini 3 do not share identical tool schemas, context behavior, or instruction priorities. A provider switch can change the solution even when your abstract prompt stays constant.
Normalize the application-level contract, not every provider-specific feature:
result = coding_agent.run(
task=task,
constraints={
"allowed_paths": ["src/discount.py"],
"required_checks": ["pytest tests/test_discount.py -q"],
},
)
if not result.tests_pass or not result.scope_ok:
result = coding_agent.run(
task=task,
provider="fallback",
start_from_clean_checkout=True,
)
Starting the fallback from a clean checkout is important. Otherwise, the second model inherits the first model’s incorrect edits and may merely rationalize them.
AI Prime Tech can be useful here when cheaper Claude, GPT, and Gemini API access makes continuous cross-model evaluation affordable. Still, retain provider and model identifiers in your telemetry; a unified endpoint should not turn distinct execution paths into an untraceable black box.
Cost your evaluations explicitly
An eval suite can become expensive if it repeatedly sends entire repositories. Use this illustrative calculation:
- 50 tasks
- 40,000 input tokens per task
- 3,000 output tokens per task
- 3 models
- Input price assumption: $3 per million tokens
- Output price assumption: $15 per million tokens
The run consumes six million input tokens and 450,000 output tokens:
Input: 50 × 40,000 × 3 / 1,000,000 × $3 = $18.00
Output: 50 × 3,000 × 3 / 1,000,000 × $15 = $6.75
Total: $24.75
Those prices are example assumptions, not a universal rate card. Substitute the current provider, cache, batch, and gateway prices before budgeting. Prompt caching can materially reduce repeated-input cost, but it can also make experiments less comparable if cache state differs between runs.
Operational best practices
For production agent systems, I use a few rules consistently:
- Pin model identifiers where the provider allows it. Floating aliases are convenient but complicate incident analysis.
- Hash the assembled prompt. Store the hash and component metadata, not necessarily sensitive prompt content.
- Make tools idempotent. Retries must not create duplicate tickets, deployments, or database writes.
- Treat compaction as a logged event. Record when it occurred, the token counts, and which constraints survived.
- Validate claims mechanically. “Tests pass” should come from a recorded exit code, not model prose.
- Keep a clean-session escape hatch. Long conversations can preserve bad assumptions even after infrastructure recovers.
- Score task outcomes over style. Correct files, passing tests, preserved APIs, and safe scope matter more than eloquence.
- Maintain provider-specific adapters. Over-normalizing tool calls usually discards useful capabilities and diagnostics.
No evaluation suite proves that a model is universally good or bad. Repository tasks are open-ended, model sampling introduces variance, and hidden runtime changes can affect results. The goal is narrower: detect meaningful regressions in workloads you actually operate.
Practical takeaways
- A Claude Code quality problem may originate in the model, but context assembly, compaction, tools, routing, and retries can produce the same symptoms.
- Preserve run metadata so subjective complaints can become segmented, reproducible cases.
- Replay real coding tasks in clean environments and grade filesystem changes, tests, and command exit codes.
- Test long-session boundaries and oversized tool results, not only short prompts.
- Use multi-model fallback deliberately, with clean state and provider-specific telemetry.
- Treat user reports as early signals, then confirm them with controlled runs rather than either dismissing them or declaring a universal model regression.
- Above all, design agent systems so a successful HTTP response is not confused with a successful engineering outcome.
One API key for Claude Opus 4.8, Sonnet 4.6, Haiku 4.5, Fable 5, plus GPT & Gemini — up to 80% off official pricing, pay-as-you-go.
Get Your API Key →