diff --git a/run_agent.py b/run_agent.py index a20a7024863..6fb7e0903e5 100644 --- a/run_agent.py +++ b/run_agent.py @@ -2143,7 +2143,10 @@ class AIAgent: # widens exposure vs the old empty-body "HTTP 400" string). response = getattr(error, "response", None) if response is not None: - snippet = (getattr(response, "text", None) or "").strip() + try: + snippet = (getattr(response, "text", None) or "").strip() + except Exception: + snippet = "" if snippet: status_code = getattr(error, "status_code", None) prefix = f"HTTP {status_code}: " if status_code else "" diff --git a/tests/run_agent/test_summarize_api_error.py b/tests/run_agent/test_summarize_api_error.py index 6739d8e48e8..ed37f7f9226 100644 --- a/tests/run_agent/test_summarize_api_error.py +++ b/tests/run_agent/test_summarize_api_error.py @@ -12,6 +12,10 @@ provider error. This is a diagnostic improvement and is platform-agnostic. from types import SimpleNamespace +from typing import Any + +import httpx + from run_agent import AIAgent @@ -54,3 +58,25 @@ def test_empty_body_fallback_redacts_secrets(monkeypatch): summary = AIAgent._summarize_api_error(err) assert "sk-proj-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdef" not in summary + +def test_unread_streaming_response_does_not_crash_and_falls_back_to_exception_message(): + """Unread streaming responses must not replace the real provider error.""" + + class _StreamingError(Exception): + def __init__(self): + super().__init__("Gemini HTTP 429: quota exceeded") + self.status_code = 429 + self.response: Any = None + + err = _StreamingError() + + class _UnreadStreamingResponse: + @property + def text(self): + raise httpx.ResponseNotRead() + + err.response = _UnreadStreamingResponse() + summary = AIAgent._summarize_api_error(err) + assert "HTTP 429" in summary + assert "Gemini HTTP 429: quota exceeded" in summary +