Skip to content

Mid-stream error events still raise APIStatusError with status_code=200 (#1258 closed without a fix) #1918

Description

@HardMax71

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions