@yibie: Recommended article: Flask author Armin Ronacher, while tracking a Pi bug, discovered a troubling fact: the tool calling of the new Claude models (Opus 4.8, Sonnet 5) is regressing—not improving but worsening. And he found the root cause: RL...

X AI KOLs Timeline News

Summary

Flask author Armin Ronacher found that the tool-calling ability of the new Claude models (Opus 4.8, Sonnet 5) is degrading. The root cause is that RL post-training over-adapts to Claude Code's own tool schema, making alternative tool schemas increasingly difficult to generate correctly. The article reveals the phenomenon of models performing worse rather than better on specific tool-calling scenarios, offering an important caution for agent development.

Recommended article: Flask author Armin Ronacher, while tracking a Pi bug, discovered a troubling fact: the tool calling of the new Claude models (Opus 4.8, Sonnet 5) is regressing—not improving but worsening. And he found the root cause: RL post-training over-adapts to Claude Code's own tool schema, making alternative tool schemas increasingly "out-of-distribution". This is an article everyone building their own agent harness needs to read. Better models, worse tool calling A strange Pi issue has led me into a deep rabbit hole over the past two days. Short version: The new Claude models sometimes add extra, fabricated fields to the nested `edits[]` array when calling Pi's edit tool. Not Haiku or some small model—this is Opus 4.8. The edits themselves are usually correct, but the parameters do not match the schema because the model invents non-existent keys. Pi rejects the tool call and asks for a retry. This is not entirely unexpected—models occasionally emit malformed tool calls, especially small ones. But what surprised me is that it has gotten worse with Anthropic's new models. Both Opus 4.8 and Sonnet 5 exhibit this issue, while earlier old models did not. In other words, the SOTA models of this family perform worse on a specific tool schema than their older siblings. Tool calling is text If you haven't spent much time looking under the hood of LLM tool calling, the important thing to understand is: tool calling is not magic. The model receives a transcript, a system prompt, and a list of available tools. The server mixes these into one big prompt with special token markers. Because the model was trained and reinforced on examples in that format, at some point during generation it emits something that is interpreted by the API or client as "call this tool with these parameters." The details: JSON inside nested arrays is serialized inside XML tags. Basic top-level string parameters appear inline, while arrays of objects are implemented via JSON serialization. This matters because when the model has to decide between `}` or `,"..."` after a few hundred tokens of escaped strings, that is the point of highest entropy. Failure Pi's edit tool supports multiple exact string replacements in a single call, so its parameters include an `edits` array. In failing cases, the model produced entries that added extra fields: `requireUnique: true`, `oldText2`, `newText2`. In repeated testing, I saw a whole batch of fabricated trailing keys: `type`, `id`, `kind`, `unique`, `requireUnique`, `matchCase`, `in_file`, `forceMatchCount`, `children`, `notes`, `cost`, and even an `event.0.additionalProperties` in there. Most annoyingly, the actual `oldText` and `newText` payloads in the invalid calls I checked were byte-identical to the correct ones. The model did produce the correct call, then appended garbage at the end of the object. This failure is also highly context-dependent. A fresh single-turn "edit this file" prompt does not reproduce it at all. It reproduces when there is an agent history—the model read the file, diagnosed the problem, then wrote a multi-line edit. And not every transcript triggers it. Enabling strict tool calling completely eliminated the issue in my runs. Why it's getting worse My strongest hypothesis is that this is not random degradation but a training artifact. When older Anthropic models were trained, they were trained on some tools, but that training did not have a user-facing harness like Claude Code as a clear target. Modern Anthropic models are likely different because their post-training includes Claude Code or a very similar harness. The model learns what tool calls succeed in that environment. It also learns what errors the environment tolerates. Claude Code's own tools are relatively flat. The typical edit tool does not have Pi's nested `edits[]` shape; it's closer to `file_path`, `old_string`, `new_string`, and an optional flag (`replace_all`). Looking at Claude Code's client is very revealing: it includes retry paths for malformed tools, parameter aliases, type coercion, Unicode repair, and filtering of unknown keys. In other words, Anthropic's own client seems to expect and accept a fair amount of slop and silently fixes it. If reinforcement learning happened within such a harness, or a simulation of it, then slightly malformed tool calls could still get the job done and receive a reward. The harness absorbs the errors almost entirely; there is hardly any gradient that penalizes "inventing an alias", "adding an extra field", or "using a similar parameter name". Worse, the model may become extremely adapted to the shape of the standard Claude Code edit tool. A different harness might provide tools that are semantically identical but with a different schema. Such tools will become increasingly out-of-distribution. A better-trained model may actually be harder to deal with because of its stronger priors. This is not entirely unexpected, but it is a shift. When Opus 4.5 was released, it was unusually good at adapting to other edit tools. At the time I was fairly confident we were on a good path—that as long as instructed well, the model would likely adapt to any kind of tool shape. Now I'm somewhat worried about which path we're on. Alternative tool schemas may not just be unfamiliar. They may be implicitly penalized by post-training that optimizes for a specific, permissive tool ecosystem. And that ecosystem is undocumented. Slop Harness Claude Code is closed-source, but we can look at the minified code. To be honest, it is very permissive with input data. First, Claude Code checks the model's visible text for leaked `<invoke` markers. Then it has its own state machine to retry bad calls. It has explicit Unicode escape repair to fix broken `\uXXXX` sequences and lone surrogates. Each tool has parameter aliases. For example, Edit accepts `old_str`, `old_string`, `new_str`/`new_string`, `path` as aliases for `file_path`. It also silently filters out unknown keys and does not use strict mode. What this means for harnesses The uncomfortable lesson is: tool schemas are not neutral, at least not on Anthropic models. We like to pretend that schemas are abstract contracts and that models are general reasoners that will obey them, but for some tools this may no longer hold. Tool schemas sit somewhere in a distribution. Some shapes are close to what the model saw in post-training, some are far. Some are easy for the provider's hidden encoding (like top-level properties in ANTML), while others require the model to write large escaped JSON objects nested inside arrays, after a long multi-line string. The model might be smart enough to understand the schema, but still fail to produce that exact shape under pressure. I used to be more skeptical of strictly grammar-constrained tool calling because constrained decoding may have quality tradeoffs. I still think this is generally true, but this bug has significantly changed my priors. If the latest models are getting better at solving problems while getting worse at faithfully generating alternative tool schemas, then harnesses need stronger guarantees somewhere. Original: Armin Ronacher, "Better Models: Worse Tools", 2026-07-04 https://lucumr.pocoo.org/2026/7/4/better-models-worse-tools/… #Claude #Agent #ToolCalling
Original Article
View Cached Full Text

Cached at: 07/05/26, 10:33 AM

Better Models, Worse Tool Calls

Flask author Armin Ronacher’s investigation of a Pi bug reveals a disturbing truth: tool calling in the new Claude models (Opus 4.8, Sonnet 5) is regressing — not getting better, but worse. And he’s found the root cause: RL post-training has over-fitted to Claude Code’s own tool schema, making alternative tool schemas increasingly off-distribution. This is a must-read for anyone building their own agent harness.

A strange Pi issue has sent me down a deep rabbit hole for the past two days. Short version: the newest Claude models sometimes add extra, hallucinated fields to the nested edits[] array when calling Pi’s edit tool. Not Haiku or some small model — Opus 4.8. The edit itself is usually correct, but the parameters don’t match the schema because the model invents keys that don’t exist, causing Pi to reject the tool call and demand a retry.

This is not entirely unexpected — models emit malformed tool calls occasionally, especially small ones. But what surprised me is that it’s getting worse with Anthropic’s new models. Opus 4.8 and Sonnet 5 both exhibit this problem, whereas previous old models did not. In other words, the SOTA model in this family is worse on a particular tool schema than its older siblings.

Tool Calls Are Just Text

If you haven’t spent much time looking under the hood of LLM tool calling, the key thing to understand is: tool calls are not magic. The model receives a transcript, a system prompt, and a list of available tools. The server stuffs these into one big prompt with special tokens. Because the model was trained and reinforced on examples in that format, at some point during generation it emits something that the API or client interprets as “call this tool with these arguments.”

The details: JSON inside nested arrays is serialized inside XML tags. Basic top-level string parameters appear inline, while arrays of objects are implemented via JSON serialization. This matters because when the model has to decide between } and , "..." after a several-hundred-token escaped string, that’s exactly the highest-entropy point.

The Failure

Pi’s edit tool supports multiple exact string replacements in one call, so the arguments contain an edits array. In the failing cases, the model produced entries like:

{ "oldText": "...", "newText": "...", "requireUnique": true }

Or:

{ "oldText": "...", "newText": "...", "oldText2": "", "newText2": "" }

Across repeated trials I saw a whole zoo of invented trailing keys: type, id, kind, unique, requireUnique, matchCase, in_file, forceMatchCount, children, notes, cost, and even an event.0.additionalProperties inside the edit object.

The most annoying part is that the actual oldText and newText payloads in the invalid calls I inspected were byte-correct. The model had produced the correct invocation but then added nonsense at the end of the object.

The failure is also heavily context-dependent. A fresh single-turn “edit this file” prompt did not reproduce it at all. With agent history — the model read files, diagnosed a problem, and composed a multi-line edit — it did reproduce. And not all transcripts show it. Turning on strict tool invocation eliminated the problem in my runs.

Why It’s Getting Worse

My strongest hypothesis is that this is not random deterioration but a training artifact.

When older Anthropic models were trained, they were trained on some tools, but that training didn’t yet have a user-shipped harness like Claude Code as the obvious target. Modern Anthropic models are most likely different because their post-training includes Claude Code or a harness that looks very similar. The model learns what a successful tool call looks like in that environment. It also learns what mistakes are tolerated.

Claude Code’s own tools are comparatively flat. The ordinary edit tool is not Pi’s nested edits[] shape; it’s closer to file_path, old_string, new_string, and an optional flag (replace_all). Looking at Claude Code’s client is very instructive: it contains retry paths for malformed tool use, parameter aliases, type coercions, Unicode repairs, and filtering of unknown keys. In other words, Anthropic’s own client appears to expect and accept a fair amount of slop and repairs it, mostly silently.

If reinforcement learning happens in a harness like that, or a simulation of one, then slightly malformed tool calls can still complete the task and receive reward. The harness fully absorbs the error and there is little gradient against inventing an alias, adding a stray field, or using a nearby parameter name.

Worse, the model may become very strongly adapted to the canonical Claude Code edit tool shape. A different harness can present a tool with the same semantic intent but a different schema. Such a tool can increasingly be off-distribution. The better-trained model might actually fight you harder because its prior is stronger.

This is not too surprising, but it is a change from how things were a few months ago. When Opus 4.5 launched, it adapted to other edit tools exceptionally well. I was pretty convinced then that we were on a good path — models were more likely to adapt to any tool shape as long as the instructions were good. Now I’m somewhat worried about the track we’re on. Alternative tool schemas might not just be unfamiliar. They might be implicitly punished by post-training that optimizes for one particular, forgiving tool ecology. And that ecology is not documented.

The Slop Harness

Claude Code is closed-source, but we can look at the minified code. Honestly, it’s very forgiving of incoming data.

For a start, Claude Code checks the model’s visible text for leaked <antml:functions> tags and attempts to verify tool use. It also has a resolve_tool_use function that normalizes the received tool call. In that function, there is:

  1. A validation step that, if that fails, does another pass of trying to find function calls in the transcript.
  2. An attempt to fix a specific Unicode encoding issue reported for Python.
  3. A step that removes extra keys from the JSON object not needed for the function call.

The fact that the client removes extra keys is interesting. That means the client expects extra keys. That’s effectively training the model to be sloppy.

Nested Parameters Are Hard

It’s worth taking a step back and looking at how tool parameters with nested arrays are handled by different providers. The general approach is quite different.

OpenAI for instance uses a JSON-based representation with a tool execution boundary. In their function calling, everything is plain JSON. The schema can be defined with JSON Schema and the model is guided to produce valid JSON. Anthropic uses a slightly different mechanism: tool calls are serialized as part of the message content using their ANTML format. In that format, the tool call is embedded as content after a special function call tag. Parameter structure is typically flat or uses JSON for nested objects.

Consider a function call like:

<antml:functions>
<antml:invoke name="edit">
<antml:parameter name="file_path">some/file.py</antml:parameter>
<antml:parameter name="edits">[{"oldText": "text to replace","newText": "replacement text"}]</antml:parameter>
</antml:invoke>
</antml:functions>

The edits parameter here is JSON serialized as a string inside an XML element. If the edits array is long or contains large strings, the model has to produce that JSON escaped inside the XML. That means it is writing a string inside a tag, and inside that string is JSON with escaped quotes and nested content. It is doing this without any grammar guidance beyond what it learned in training.

An important thing to note here: while this looks like XML, it’s not really XML. It’s just a thing they found convenient to tokenize and train on. The other thing to note is that a basic top-level string parameter appears inline, whereas an array of objects is implemented via JSON serialization. While I’m not entirely sure this is exactly how it works, there are indications it’s not too far off.

There are two very different ways to make the model produce such a structure:

  1. You can ask the model to produce valid JSON matching a schema and validate afterwards.
  2. You can constrain the sampler so that invalid JSON or invalid schema shapes cannot be sampled.

The second approach is grammar-aware or constrained decoding. The sampler masks tokens that would violate the grammar. Grammar-aware decoding can enforce syntactically valid JSON, specific enum values, or keys.

Without constraints, the model is merely following a learned convention. Harmony, the protocol used by Github’s Copilot and OpenAI’s Codex in their agent mode, uses a protocol like this:

assistant<|channel|>commentary to=functions.get_weather <|constrain|>json<|message|>{"location":"San Francisco"}<|call|>

The important bit is <|constrain|>json. The model can express in-band that the message body is JSON, and the inference stack can switch into JSON-constrained sampling. Some of this also happens in Anthropic’s models, at least in strict mode. The marker helps the sampler detect when to use a specific grammar and makes it easy to do.

For hosted GPT models, there is an option to provide a LARK grammar for custom tools. Anthropic seems different, though maybe not entirely. If arrays of objects are represented as JSON, then the model writes JSON inside the tool parameter. There may be basic grammar-constrained sampling, which could partly explain the extra keys. For a nested array parameter, that JSON includes escaped multi-line file content inside string literals. The unexpected keys appear exactly at the highest-entropy point: after closing a several-hundred-token escaped newText string, where the model must decide } vs , "...".

Opus 4.8 and Sonnet 5 seem to have much stronger priors about what an edit tool call should look like — and that prior appears to be Claude Code’s flat old/new string pair plus the optional replace_all flag. My guess is that Opus has learned that an edit operation may have one extra optional field, but under Pi’s nested oldText/newText shape it has no trained name for that field. So it samples a plausible name fresh each time, producing dozens of random keys rather than one stable alias.

Since strict mode in Anthropic appears to fix this, I presume the server side refuses to sample a key not permitted by the JSON schema. That would explain why they have limits on tool definition complexity when strict mode is enabled.

So far, the Codex models I tested did not show this regression. I tested all available ones except 5.6, which I don’t have access to yet.

What This Means For Harnesses

The uncomfortable lesson is that tool schemas are not neutral, at least on Anthropic models. We like to pretend a schema is an abstract contract and the model follows it as a general reasoner, but that may no longer be the case. Tool schemas lie somewhere in the distribution. Some shapes are close to post-training, some are far. Some are easy for the provider’s hidden encoding (e.g., top-level attributes in ANTML), while others require large escaped JSON objects inside nested arrays after long multiline strings. The model may understand the schema but still be bad at sampling the exact shape under pressure.

If this continues, what are the implications for harnesses? You could turn on strict sampling in Anthropic and the problem should go away. But that the model exhibits this behavior reveals the impact of reinforcement learning. Fighting that prior is probably futile if you want the best performance.

Right now, Claude Code is not open source, and we can’t know what they do in their RL environments. We can’t assume Claude-Code-trained behavior will transfer cleanly to your tools unless they are a close match. The more post-training happens inside one dominant harness, the more every other harness must inherit its quirks.

I used to be more skeptical of strict grammar-constrained tool invocation because constrained decoding can have quality tradeoffs. That may still be true in general, but this bug significantly shifted my priors. If the newest models get better at solving the task while getting worse at faithfully emitting an alternative tool schema, then the harness needs stronger guarantees somewhere.

If you want to find out more, or discuss this, consider reading the issue on the Pi tracker.

This entry was tagged ai and pi.

copy as markdown / view

Similar Articles

Better Models: Worse Tools

Hacker News Top

Newer Claude models (Opus 4.8 and Sonnet 5) exhibit worse tool-calling behavior by inventing extra fields in tool invocation arguments, causing validation failures, a regression compared to older models.

Better Models: Worse Tools

Simon Willison's Blog

Newer Anthropic models like Opus 4.8 and Sonnet 5 are worse at using third-party editing tools (e.g., Pi's) compared to older models, likely because they were trained to use Claude Code's built-in edit tool via RL, causing them to invent extra fields in tool calls.

@xiaohu: Claude Code's father's own CLAUDE.md is now just two lines... Claude Code team discusses "less is more" sharing how to communicate with models as capabilities increase: "Don't fight the model by adding more, because each generation of models gets stronger. What you painstakingly build today will soon be useless."

X AI KOLs Timeline

Claude Code team shares best practices: CLAUDE.md should be as short as possible and regularly cleared; insists on CLI over GUI because models improve too fast; using AI to fix bugs is already remarkably efficient. Core strategy: subtract, keep configuration light, and trust model capabilities.

@AlchainHust: https://x.com/AlchainHust/status/2064676532212097418

X AI KOLs Timeline

This article provides a detailed review of Anthropic's newly released Claude Fable 5 model, and demonstrates the author's process of using it to develop a Mac App '翻箱' in one day. The model has significant improvements in code generation and stability.

@CycleDecoded: Ridiculous, guys—a wild trick just stormed GitHub trending, directly exploiting a bug in the large model billing system. This thing is called pxpipe (MIT license), a local open-source proxy tool specifically designed to counter Claude Code's billing shock. The principle is absolutely genius: large models charge text tokens by word count, but images are billed by fixed pixels. So it simply takes your long, bloated system prompts, code, and history logs, snaps them into a dense PNG image, and feeds it through the model's vision channel. This is a brutal move—a direct bypass of expensive text billing!

X AI KOLs Timeline

pxpipe is a local open-source proxy tool that reduces Claude Code bills by approximately 70% by rendering large amounts of text (such as system prompts, code, and logs) into PNG images and feeding them through the large model's vision channel. It exploits the fact that images are billed by pixel rather than by word count. The tool is perfectly compatible with the Fable 5 model, operates with clever automation, but uses lossy compression and is unsuitable for sensitive data.