Bug
When the API returns HTTP 200 (streaming started fine) but then sends an SSE error event like overloaded_error, the SDK creates an APIStatusError with status_code=200 instead of 529. The error body has the right type, but the status code is wrong.
This happens because _streaming.py passes self.response (the original HTTP response object) to _make_status_error, and _make_status_error switches on response.status_code to pick the error subclass.
Code path
In _streaming.py (line ~229):
if sse.event == "error":
body = sse.json()
err_msg = f"{body}"
raise self._client._make_status_error(
err_msg,
body=body,
response=self.response, # <-- this is the original HTTP 200 response
)
In _client.py, _make_status_error dispatches on response.status_code:
def _make_status_error(self, err_msg, *, body, response):
if response.status_code == 429:
return RateLimitError(...)
if response.status_code >= 500:
return InternalServerError(...)
return APIStatusError(...) # <-- falls through here because status is 200
Since the HTTP status was 200, none of the specific branches match. You get a bare APIStatusError with status_code=200.
Why this matters
Any caller checking status_code for retry/fallback decisions gets the wrong answer. An overloaded_error should look like a 529, not a 200. The body has the correct error type ({'error': {'type': 'overloaded_error'}}), but most callers don't expect to need to parse that — they check status codes.
We hit this in production with pydantic-ai's FallbackModel, which checks status_code >= 500 to decide whether to try the next model. The overloaded error had status_code=200, so fallback never fired.
Expected behavior
Mid-stream SSE errors should produce the same error subclass as if the error came back as an HTTP response. An overloaded_error → OverloadedError(status_code=529), an api_error → InternalServerError(status_code=500), etc.
Suggested fix
Map the SSE error type to the corresponding status code before calling _make_status_error, or construct the right error subclass directly:
_SSE_ERROR_TYPE_TO_STATUS = {
"overloaded_error": 529,
"rate_limit_error": 429,
"api_error": 500,
"internal_server_error": 500,
# client errors (shouldn't appear mid-stream, but for completeness):
"authentication_error": 401,
"invalid_request_error": 400,
"not_found_error": 404,
}
Then use the mapped status code (falling back to the original response.status_code) when building the error.
Versions
- anthropic SDK: 0.52.0
- Observed with claude-sonnet-4-20250514 via the streaming API
Bug
When the API returns HTTP 200 (streaming started fine) but then sends an SSE error event like
overloaded_error, the SDK creates anAPIStatusErrorwithstatus_code=200instead of 529. The error body has the right type, but the status code is wrong.This happens because
_streaming.pypassesself.response(the original HTTP response object) to_make_status_error, and_make_status_errorswitches onresponse.status_codeto pick the error subclass.Code path
In
_streaming.py(line ~229):In
_client.py,_make_status_errordispatches onresponse.status_code:Since the HTTP status was 200, none of the specific branches match. You get a bare
APIStatusErrorwithstatus_code=200.Why this matters
Any caller checking
status_codefor retry/fallback decisions gets the wrong answer. Anoverloaded_errorshould look like a 529, not a 200. The body has the correct error type ({'error': {'type': 'overloaded_error'}}), but most callers don't expect to need to parse that — they check status codes.We hit this in production with pydantic-ai's
FallbackModel, which checksstatus_code >= 500to decide whether to try the next model. The overloaded error hadstatus_code=200, so fallback never fired.Expected behavior
Mid-stream SSE errors should produce the same error subclass as if the error came back as an HTTP response. An
overloaded_error→OverloadedError(status_code=529), anapi_error→InternalServerError(status_code=500), etc.Suggested fix
Map the SSE error type to the corresponding status code before calling
_make_status_error, or construct the right error subclass directly:Then use the mapped status code (falling back to the original
response.status_code) when building the error.Versions