[stream-json] Emit Anthropic API errors as a distinct event type, not as assistant text blocks

Resolved 💬 1 comment Opened Apr 26, 2026 by axonova-bot Closed May 29, 2026

Summary

When claude -p --output-format stream-json encounters an Anthropic API error
(rate_limit_error, authentication_error, overloaded_error, etc.), the CLI emits
the error as the text of an assistant content block:

{"type":"assistant","message":{"content":[{"type":"text","text":"API Error: Request rejected (429) · ..."}]}}

This makes it impossible for downstream parsers to distinguish "Claude said
something" from "Anthropic returned an error" without text-matching the
content. A distinct stream event would be much cleaner:

{"type":"error","error":{"type":"rate_limit_error","message":"...","http_status":429,"request_id":"req_..."}}

Why this matters for downstream consumers

Anyone building on top of claude -p stream-json today has to either:

  1. Pattern-match "API Error:" and the JSON envelope across assistant text

blocks, which is brittle (formats vary across error types — see
https://code.claude.com/docs/en/errors for the variety) and silently
breaks when the CLI's wrapping changes.

  1. Treat error replies as real assistant content. For tools that store

conversation history, the error blob ends up in the persisted record as
if it were a model reply, which corrupts downstream features like vector
recall, summarization, and continued context.

A structured error event would let consumers route errors via type-safe
event-type dispatch — same as assistant / result / tool_use are today.

Concrete shape proposal

{
  "type": "error",
  "error": {
    "type": "rate_limit_error",
    "message": "Request rejected (429) · ...",
    "http_status": 429,
    "request_id": "req_011CSHoEeqs5C35K2UUqR7Fy"
  },
  "model": "claude-opus-4-7",
  "fatal": true
}

fatal: true would distinguish errors that ended the run from transient
errors the CLI retried internally (which today don't surface to consumers
at all — they only see the final outcome).

Backwards compatibility

Existing consumers that read assistant text blocks would not see the
error blob there anymore. To smooth the transition, the CLI could emit
BOTH events (assistant text with the blob AND the structured error event)
for one or two minor versions, with a clear deprecation note that the
assistant-text form will be dropped.

Workaround we're using today

We parse the error envelope (or known HTTP code regex) inside the assistant
text block and route it to a structured field at the subprocess boundary.
It works but it's a band-aid that has to be maintained against any CLI
rendering changes — happy to delete it the day this ships upstream.

Affected versions

Observed in v2.1.x. The wrapping format itself is roughly stable across
recent releases but the variety of message shapes (verbatim from the docs):

  • API Error: 500 {"type":"error","error":{"type":"api_error",...}} · check status.claude.com
  • API Error: Repeated 529 Overloaded errors · check status.claude.com
  • API Error: Request rejected (429) · this may be a temporary capacity issue
  • API Error: 401 ... authentication_error

makes a stable parser hard to write.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗