NousResearch/hermes-agent

Streaming error body read crash in API error handler

Cluster 59769. Data as of

Nothing open on GitHub. Fix merged in #61112. Check it covers your case.

The short answer

0open issues
0open PRs
5items grouped
Known?
Yes. 5 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #61112. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #61112. That is separate from the fix event.
Still open
Nothing open on GitHub.

A bug where _summarize_api_error crashes with httpx.ResponseNotRead when handling streaming errors (such as Gemini free-tier 429s), hiding the real error message. Multiple fix attempts guard the response.text access against this unread-response condition.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #61112 fix(agent): guard response.text access in _summarize_api_error against httpx.ResponseNotRead

    merged to main

    files: run_agent.py, scripts/release.py, tests/run_agent/test_summarize_api_error.py

    Show the diff
    • run_agent.py
      @@ -2143,7 +2143,10 @@ def _summarize_api_error(error: Exception) -> str:
               # 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 ""
    • scripts/release.py
      @@ -59,6 +59,7 @@
           "[redacted:email]": "ManniBr",  # PR #57417 partial salvage (gateway: fail-closed adapter resolution for unregistered secondary profiles)
           "[redacted:email]": "austinlaw076",  # PR #57563 partial salvage (auth: lazy per-profile Anthropic OAuth file; gateway: whatsapp_cloud/line added to port-binding platform set)
           "[redacted:email]": "liuhao1024",  # PR #56993 salvage (gateway: process-level HERMES_HOME for pid/lock/status identity files; #56986)
      +    "[redacted:email]": "gauravsaxena1997",  # PR #59868 partial salvage (agent: guard response.text against httpx.ResponseNotRead in _summarize_api_error; #59769)
           "[redacted:email]": "irresi",  # PR #59048 salvage (gateway: scope reset banners' session info to the serving profile; #59003)
           "[redacted:email]": "s905060",  # PR #57943 salvage (photon: auto-reinstall stale sidecar node_modules when lockfile is newer than npm's install marker; #59169)
           "[redacted:email]": "l0h1nth",  # PR #32210 salvage (mattermost: accept leading-space slash commands from mobile clients; #25184)
    • tests/run_agent/test_summarize_api_error.py
      @@ -11,6 +11,9 @@
       """
       
       from types import SimpleNamespace
      +from typing import Any
      +
      +import httpx
       
       from run_agent import AIAgent
       
      @@ -54,3 +57,25 @@ def test_empty_body_fallback_redacts_secrets(monkeypatch):
           summary = AIAgent._summarize_api_error(err)
           assert "[redacted:openai-key]" 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
      +

3 PRs closed without merging

From the close comments: merged via another change 2 · superseded by another PR 1

  • #59774 fix(agent): guard response.text access in _summarize_api_error against httpx.ResponseNotRead · superseded by another PR → #61112, #59868
  • #59791 fix(agent): handle unread streaming response in _summarize_api_error (#59769) · merged via another change → #59868, #61112
  • #60192 fix(agent): guard response.text access in _summarize_api_error against ResponseNotRead (#59769) · merged via another change → #59868, #61112
Closed issues · 1
  • Closed#59769[Bug]: _summarize_api_error crashes with httpx.ResponseNotRead on Gemini streaming errors, hiding the real message (e.g. free-tier quota 429s)
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.40: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Merged PRs
#61112
Closed issues
1
Attempts
3 PRs closed without merging
Fix link checked
8 Jul 2026

For your agent

The same facts, typed.