Correction (same day): #1258 was not closed without a fix. #1587 added APIStatusError.type, which carries the in-stream error type (overloaded_error here), and the maintainers chose that over synthesizing a status code. What this issue is really about is the class: an in-stream error is still the bare APIStatusError, never OverloadedError / RateLimitError, so class-based handling misses it. The status code staying 200 is by design. PR that only picks the class from error.type: #1921
Follow-up to #1258, closed as completed on 2026-03-31. The behaviour is unchanged on 0.122.0, 1.4.0 and main.
When the API sends an error event inside a stream, _streaming.py passes the original 200 response to _make_status_error, which dispatches on response.status_code only, so the result is a bare APIStatusError with status_code=200. For an overloaded_error that means OverloadedError (529) is never raised in-stream, and anything that keys retries on the exception class or the status code sees a 200 "error". Wrappers end up with "overloaded_error" in str(e) (agno does exactly that) because nothing else is stable.
Offline repro, no network:
import platform
import sys
import anthropic
try:
import httpx2 as httpx # anthropic >= 1.0
except ImportError:
import httpx
SSE = (
'event: message_start\ndata: {"type":"message_start","message":{"id":"msg_1","type":"message","role":"assistant","model":"claude-sonnet-4-6","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":1,"output_tokens":0}}}\n\n'
'event: error\ndata: {"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}\n\n'
)
def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=SSE.encode())
client = anthropic.Anthropic(api_key="x", http_client=httpx.Client(transport=httpx.MockTransport(handler)), max_retries=0)
print(f"python {sys.version.split()[0]} {platform.system()}, anthropic {anthropic.__version__}")
try:
with client.messages.stream(model="claude-sonnet-4-6", max_tokens=8, messages=[{"role": "user", "content": "hi"}]) as s:
for _ in s:
pass
except anthropic.APIStatusError as e:
print(f"raised {type(e).__name__} status_code={e.status_code} body={e.body}")
print(f"has OverloadedError class: {hasattr(anthropic, 'OverloadedError')}")
Output on 0.122.0 and 1.4.0 (python 3.12.13, macOS):
raised APIStatusError status_code=200 body={'type': 'error', 'error': {'type': 'overloaded_error', 'message': 'Overloaded'}}
has OverloadedError class: True
Expected: the in-stream error mapped from body["error"]["type"] (overloaded_error to OverloadedError / 529, rate_limit_error to RateLimitError / 429, api_error to InternalServerError / 500, and so on), with the 200 response still attached. #1263 and #1269 did roughly that and were closed unmerged. Happy to send a fresh PR in whatever shape the "better approach" mentioned in #1258 was meant to take.
Correction (same day): #1258 was not closed without a fix. #1587 added
APIStatusError.type, which carries the in-stream error type (overloaded_errorhere), and the maintainers chose that over synthesizing a status code. What this issue is really about is the class: an in-stream error is still the bareAPIStatusError, neverOverloadedError/RateLimitError, so class-based handling misses it. The status code staying 200 is by design. PR that only picks the class fromerror.type: #1921Follow-up to #1258, closed as completed on 2026-03-31. The behaviour is unchanged on 0.122.0, 1.4.0 and main.
When the API sends an
errorevent inside a stream,_streaming.pypasses the original 200 response to_make_status_error, which dispatches onresponse.status_codeonly, so the result is a bareAPIStatusErrorwithstatus_code=200. For anoverloaded_errorthat meansOverloadedError(529) is never raised in-stream, and anything that keys retries on the exception class or the status code sees a 200 "error". Wrappers end up with"overloaded_error" in str(e)(agno does exactly that) because nothing else is stable.Offline repro, no network:
Output on 0.122.0 and 1.4.0 (python 3.12.13, macOS):
Expected: the in-stream error mapped from
body["error"]["type"](overloaded_error toOverloadedError/ 529, rate_limit_error toRateLimitError/ 429, api_error toInternalServerError/ 500, and so on), with the 200 response still attached. #1263 and #1269 did roughly that and were closed unmerged. Happy to send a fresh PR in whatever shape the "better approach" mentioned in #1258 was meant to take.