From cdcffc6ec43fc01c6f10ac521a3598083495765e Mon Sep 17 00:00:00 2001 From: ggzeng Date: Thu, 25 Jun 2026 13:20:30 +0800 Subject: [PATCH] fix(proxy): map non-standard SSE error codes (>=600) to 502 for failure handling Previously, _parse_event_data_for_error() silently dropped SSE error codes outside the standard HTTP range (100-599), returning None. This caused the proxy to treat error-bearing SSE chunks as normal content: - The error was forwarded to the client as a 200 response - Router fallback/cooldown never triggered - Subsequent requests kept hitting the failing deployment This affects providers that use vendor-specific error codes, such as: - ZAI/ZhipuAI: 1302 (rate limit), 1305, 1308 (5-hour quota) - DashScope: 4-digit throttling codes - Other non-RFC-compliant providers Fix: map error codes >= 600 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. Codes < 100 are still treated as invalid (None) since they are likely corrupt data, but the diagnostic warning is preserved (addressing the Greptile P2 review feedback on the original implementation). This fix has been running in production on a v1.89.0 source-mount deployment with ZAI/ZhipuAI as the primary provider for 1+ days. Before the fix, ZAI 1302 (rate limit) errors appeared 855 times in anomaly logs with zero cooldown/fallback triggers. After the fix, rate-limit errors correctly trigger deployment cooldown, and subsequent requests are routed to fallback providers. Fixes #31284 --- litellm/proxy/common_request_processing.py | 36 ++++++++++++++++--- .../proxy/test_common_request_processing.py | 12 ++++--- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index 97f7d51970c..b6483b5e563 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -239,10 +239,38 @@ async def _parse_event_data_for_error(event_line: Union[str, bytes]) -> Optional # Not a valid integer string, treat as if no valid code was found for this check pass - # 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( + f"SSE error code {error_code} is outside standard HTTP range " + f"(100-599); mapping to 502 for failure handling" + ) + 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(f"Error has invalid or non-convertible code: {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(f"Error has invalid or non-convertible code: {error_code_raw}") except (orjson.JSONDecodeError, json.JSONDecodeError): # not a known error chunk diff --git a/tests/test_litellm/proxy/test_common_request_processing.py b/tests/test_litellm/proxy/test_common_request_processing.py index 1d0dafed171..5a3db54520d 100644 --- a/tests/test_litellm/proxy/test_common_request_processing.py +++ b/tests/test_litellm/proxy/test_common_request_processing.py @@ -1118,11 +1118,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,