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
This commit is contained in:
ggzeng 2026-06-25 13:20:30 +08:00 • committed by Gavin Zeng
parent 710f6b2dcd
commit cdcffc6ec4
2 changed files with 40 additions and 8 deletions

View file

@ -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

View file

@ -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,