From 1adc4112602cabcdfc4c26a17537ff72d647abe2 Mon Sep 17 00:00:00 2001 From: narmstrong-berri Date: Fri, 6 Nov 2026 15:12:39 +0000 Subject: [PATCH] fix(messages): state no headers for a native failure without wire headers A failure the native path raises without an HTTP error response, such as an unreadable 200 body or a header value the client refuses to send, carries no wire headers, and Python logs none for it. The rebuilt response added a content-length from the error text --- litellm/rust_bridge/host/routes/messages.py | 2 ++ tests/unit/rust_bridge/host/routes/test_messages.py | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/litellm/rust_bridge/host/routes/messages.py b/litellm/rust_bridge/host/routes/messages.py index d893b029069..f15a4d616ee 100644 --- a/litellm/rust_bridge/host/routes/messages.py +++ b/litellm/rust_bridge/host/routes/messages.py @@ -186,6 +186,8 @@ def _upstream_failure( headers: Final = _UPSTREAM_HEADERS.validate_python(getattr(error, "headers", None)) except ValidationError: return None + if not headers: + return status, text, headers # Python's `MaskedHTTPStatusError` rebuilds the response from its decoded body without the # wire's content-encoding and content-length, so httpx states a length only for a non-empty body. rebuilt: Final = httpx.Response( diff --git a/tests/unit/rust_bridge/host/routes/test_messages.py b/tests/unit/rust_bridge/host/routes/test_messages.py index 7ecd18fa48e..80bbd8832f6 100644 --- a/tests/unit/rust_bridge/host/routes/test_messages.py +++ b/tests/unit/rust_bridge/host/routes/test_messages.py @@ -243,6 +243,19 @@ def test_upstream_failure_headers_match_the_python_clients_rebuilt_response( assert dict(_get_response_headers(mapped) or {}) == expected +@pytest.mark.parametrize("status", [200, 500]) +def test_upstream_failure_without_wire_headers_states_no_headers(status: int) -> None: + from litellm.litellm_core_utils.exception_mapping_utils import _get_response_headers + + upstream: Final = ValueError(status, "not a messages response") + upstream.headers = [] # pyright: ignore[reportAttributeAccessIssue] # set by the native host + + mapped: Final = messages_host.map_failure(upstream, {"model": _ADAPTIVE_OPUS}, "anthropic") + + assert isinstance(mapped, Exception) + assert not dict(_get_response_headers(mapped) or {}) + + def test_a_late_rejection_tells_the_logger_the_resolved_call_first() -> None: import datetime