diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index 5ae6c628324..f33b8692342 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -1035,10 +1035,38 @@ async def _parse_event_data_for_error(event_line: str | bytes) -> int | None: ) # Not a valid integer string, treat as if no valid code was found for this check - # Ensure error_code is a valid HTTP status code - if error_code is not None and 100 <= error_code <= 599: - return error_code - elif error_code_raw is not None: # Log if original code was present but not valid + if error_code is not None: + if 100 <= error_code <= 599: + # Standard HTTP status code + return error_code + elif error_code >= 600: + # Vendor-specific error codes outside the standard HTTP + # range (e.g., ZAI/ZhipuAI 1302 rate limit, DashScope + # throttling codes). Previously these were silently + # dropped (returned None), causing the proxy to treat + # the SSE chunk as normal content — the error was + # forwarded to the client as a 200 response, and router + # fallback/cooldown never triggered. + # + # Map to 502 (Bad Gateway) so that failure handling + # (retry, fallback, cooldown) fires correctly. The + # original vendor code is preserved in the warning log + # for debugging. + verbose_proxy_logger.warning( + "SSE error code %s is outside standard HTTP range (100-599); mapping to 502 for failure handling", + error_code, + ) + return 502 + else: + # Integer code below 100 (or negative) — likely corrupt data + # but keep the original behaviour of returning None while + # emitting the diagnostic warning (preserved from the + # original elif error_code_raw is not None: branch). + verbose_proxy_logger.warning("Error has invalid or non-convertible code: %s", error_code) + elif error_code_raw is not None: + # error_code could not be coerced to int from the raw value + # (e.g., a non-numeric string). Log the original raw value + # for debugging and fall through to return None. verbose_proxy_logger.warning("Error has invalid or non-convertible code: %s", error_code_raw) except (orjson.JSONDecodeError, json.JSONDecodeError): # not a known error chunk diff --git a/tests/unit/proxy/test_common_request_processing.py b/tests/unit/proxy/test_common_request_processing.py index 13f6f840cb7..ada3b10057e 100644 --- a/tests/unit/proxy/test_common_request_processing.py +++ b/tests/unit/proxy/test_common_request_processing.py @@ -2283,11 +2283,15 @@ class TestCommonRequestProcessingHelpers: ( 'data: {"error": {"code": 99, "message": "too low"}}', None, - ), # Integer code too low + ), # Integer code too low (< 100, likely invalid data) ( - 'data: {"error": {"code": 600, "message": "too high"}}', - None, - ), # Integer code too high + 'data: {"error": {"code": 600, "message": "vendor error"}}', + 502, + ), # Non-standard code >= 600 → mapped to 502 for failure handling + ( + 'data: {"error": {"code": 1302, "message": "rate limit"}}', + 502, + ), # Vendor-specific rate limit code (ZAI/ZhipuAI) → mapped to 502 ( 'data: {"id": "123", "content": "hello"}', None,