Build1 distinct publisher3 min readUpdated
A dev.to field-notes post lays out how partial_json actually arrives, and why the three common ways of handling it fail in three different places.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
A set of field notes published on dev.to under the headline "Streaming tool calls without losing your mind" draws a line that most agent codebases cross on the first pass: with the Anthropic API, a streamed tool call does not arrive as one JSON blob, it arrives as raw JSON text fragments [2][1]. Anything that renders or acts on the accumulated buffer before the block closes is operating on invalid JSON, and per the post it fails every time rather than occasionally [5].
The event shape is specific. A `content_block_start` of type `tool_use` carries a name and an empty `input`, then a run of `content_block_delta` events whose `delta.type` is `input_json_delta` each carry a fragment of the arguments as raw text in `partial_json` [3]. As the author puts it, you get the characters of the JSON object, not the object [4]. Three deltas in, your buffer might read `{"target": "sta`, and `json.loads` on that raises `JSONDecodeError` until the block actually closes [5]. Only at `content_block_stop` is the accumulated string guaranteed parseable [6]. That is a parser behaving correctly on invalid input, not a bug in your handling [7].
The reason teams walk into this is that plain-text streaming is a solved problem, because a partial sentence is still readable, and tool calls look like the same job [8]. They are not, and the post enumerates three coping strategies that trade off differently [9]. Two of the three it rejects outright, and the third it accepts only for execution [2].
First: accumulate, call `json.loads` after every chunk, catch the exception, continue. It does not crash, but you are running exception-driven control flow on the hot path of every tool call, dozens of times per call, and it hides the one exception that matters, a genuinely malformed final payload; the log line you care about becomes indistinguishable from noise [10].
Second: buffer everything and parse once at `content_block_stop`. Correct, and the right thing for execution, but if that is all you do you have opted out of streaming for tool calls while your text responses still stream token by token [11]. On a tool call with a large argument, a long file body or a multi-paragraph message draft, the user watches nothing for the entire generation and then sees the whole result at once [12].
Third: guess the shape by string matching, tracking open braces, counting quotes, treating a comma at depth 1 as the end of a value. It survives the happy path and breaks on the first argument value containing a brace, an escaped quote, or a comma of its own, which for free text is a question of when [13].
The recommended split is to stop treating parse-for-display and parse-for-execution as one operation, because they have different tolerance for being wrong [14]. Display wants a tolerant read of an incomplete document, good enough for a progress skeleton and never good enough to act on [15]. The post's `best_effort_partial` helper appends a closing quote when the quote count is odd, closes whatever brackets remain open, and returns `None` when the result still will not parse, with a docstring warning never to feed the output to anything that executes [16]. Run it per delta, render an incrementally filling form, and skip the frame when it returns `None` [17]. That leaves two parse paths per tool call with two contracts [1].
Worth checking in your own stack: whether a swallowed `JSONDecodeError` sits on the tool-call hot path [10], whether any display-side tolerant parse can reach an executor [16], and which of your tools carry arguments large enough that end-of-block parsing shows up as dead air [12].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
With the Anthropic API, a streamed tool call does not show up as one JSON blob; the arguments arrive as raw JSON text fragments across events.
The material is a field-notes post published on dev.to under the headline "Streaming tool calls without losing your mind", originally published on Loop & Retry, described as field notes on building LLM agents that survive production.
A streamed Anthropic tool call appears as a content_block_start of type tool_use (with a name and an empty input), followed by a run of content_block_delta events whose delta.type is input_json_delta, each carrying a fragment of the arguments as raw text in partial_json.
"You get the characters of the JSON object, not the object."
After three deltas the accumulated buffer might read {"target": "sta ; feeding that to json.loads produces a JSONDecodeError, every time, until the block actually closes.
Only at content_block_stop is the accumulated raw string guaranteed parseable.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Self-consistent first-hand account, uncorroborated
The mechanism is described with specific, checkable detail — named event types, delta.type input_json_delta, partial_json fragments, a runnable accumulation loop and the completer helper — which is strong for a practitioner note. But it is one authored post with no vendor documentation, SDK reference, benchmark, or second publisher confirming either the event sequence or the recommended split, so evidence sits mid-range rather than high.
No adoption evidence supplied
The cluster contains no release, deployment, usage disclosure, benchmark, or telemetry showing that anyone beyond the author runs this pattern; the only usage signal is a sample snippet naming claude-sonnet-4-5. Adoption is therefore not measurable from the supplied material.
Claims sized to the evidence
The post's claims are narrow and mechanism-level — how partial_json arrives, why intermediate parses fail, and three named trade-offs — with no market-size, benchmark, or breakthrough framing. It also states its own limits, calling the display/execution divergence cosmetic and the tolerant parse unusable for execution, so rhetoric and support are roughly aligned.
Mild self-promotion, no vendor stake shown
The item opens by attributing itself to the author's own publication, Loop & Retry, so there is an audience-building incentive behind a syndicated post on dev.to. Nothing in the supplied material indicates vendor sponsorship, a product being sold, or a commercial relationship with Anthropic, so the incentive load reads low.
Plausible and specific, single-source
Internal consistency between prose and code is high and the technical claims are the kind readily checked against an SDK, which supports moderate confidence. It is capped by a one-publisher cluster, absent adoption evidence, and no external verification of the described event sequence.
build
The refund button is the architecture: inside the tool-use layer of a support agent1 distinct publisher
build
Nobody chose retry-by-default, and the bill arrives as your customer's timeout1 distinct publisher
build
Count invalid JSON as a failed classification, and model choice becomes a reliability problem1 distinct publisher
product
The cheapest model scored 10 out of 100: assistant choice is now a code-security decision1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 18, 2026