NousResearch/hermes-agent

QQBot closed WebSocket causes busy-loop, stalled reconnects

Cluster 17703 · gateway. Data as of

2 open items. Fix merged in #40574 on 7 Jun 2026. Check it covers your case.

The short answer

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

Members consistently concern the QQBot adapter's _read_events/_listen_loop misbehaving when the WebSocket is closed but not None after a network outage or an unsuccessful reconnect, reported as a 100% CPU busy-loop or reconnection stopping. Many are proposed fixes, such as raising an error or adding guards, and some issues mention a similar WeCom pattern.

Open issues1

  • Issue#55492QQBot _read_events has same silent-return pattern as WeCom busy-loop (mitigated but inconsistent)

Open PRs1

  • PR#55524fix(qqbot): add post-loop guard in _read_events to prevent backoff reset

Fixes on record

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

  • #40574 fix(qqbot): stop 100% CPU spin when WebSocket is closed but not None (#31193, #31771)

    merged to main

    files: gateway/platforms/qqbot/adapter.py, tests/gateway/test_qqbot.py

    Show the diff
    • gateway/platforms/qqbot/adapter.py
      @@ -681,6 +681,12 @@ async def _read_events(self) -> None:
               """Read WebSocket frames until connection closes."""
               if not self._ws:
                   raise RuntimeError("WebSocket not connected")
      +        if self._ws.closed:
      +            # A closed-but-non-None ws makes the while-condition false on entry,
      +            # so this would return normally — which _listen_loop treats as a
      +            # clean read and immediately retries with backoff reset to 0,
      +            # producing a 100% CPU spin. Raise so the reconnect/backoff path runs.
      +            raise RuntimeError("WebSocket closed")
       
               while self._running and self._ws and not self._ws.closed:
                   msg = await self._ws.receive()
    • tests/gateway/test_qqbot.py
      @@ -2196,3 +2196,27 @@ def test_fatal_codes_include_intent_errors(self):
               assert 4014 in fatal_codes
               assert 4001 in fatal_codes
               assert 4915 in fatal_codes
      +
      +
      +class TestReadEventsClosedWsGuard:
      +    """Regression: a closed-but-non-None ws must raise on entry, not return
      +    normally, so _listen_loop goes through reconnect/backoff instead of
      +    busy-looping at 100% CPU (issues #31193 / #31771)."""
      +
      +    def _make_adapter(self, **extra):
      +        from gateway.platforms.qqbot import QQAdapter
      +        return QQAdapter(_make_config(app_id="a", client_secret="b", **extra))
      +
      +    def test_read_events_raises_when_ws_closed_on_entry(self):
      +        adapter = self._make_adapter()
      +        adapter._running = True
      +        adapter._ws = SimpleNamespace(closed=True)
      +        with pytest.raises(RuntimeError):
      +            asyncio.run(adapter._read_events())
      +
      +    def test_read_events_raises_when_ws_none(self):
      +        adapter = self._make_adapter()
      +        adapter._running = True
      +        adapter._ws = None
      +        with pytest.raises(RuntimeError):
      +            asyncio.run(adapter._read_events())

9 PRs closed without merging

From the close comments: done elsewhere 6 · duplicate 2 · merged via another change 1

  • #17704 fix(qqbot): keep reconnecting after closed websocket · done elsewhere → 3eeca46, #17704, #31193, #31771, #40574
  • #27821 fix(qqbot): prevent busy-loop when WebSocket closes without exception · done elsewhere → 3eeca46, #31193, #31771, #40574
  • #31151 fix(qqbot): raise RuntimeError in _read_events when WebSocket is closed · duplicate → #17704, #17703, #25070, #20994
  • #31774 fix(qqbot): prevent listener busy-loop after closed-WebSocket reconnect failure (#31771) · merged via another change → 3eeca46, #31771, #40574
  • #32885 fix(qqbot): prevent reconnect stall after closed websocket · done elsewhere → 3eeca46
Show 4 more
  • #39430 fix(qqbot): prevent CPU-spinning tight loop after WebSocket reconnect failure (#17703) · done elsewhere → #41773
  • #40198 fix(gateway): keep QQBot reconnect loop alive after stale websocket · done elsewhere → 3eeca46
  • #40497 fix(qqbot): prevent busy-loop when WebSocket is closed but not None · done elsewhere → 8dca287
  • #55601 fix(qqbot): raise on silent loop exit to keep _listen_loop consistent · duplicate → #55524, #55492, #55486
Closed issues · 6
  • Closed#17703QQBot stops reconnecting after failed reconnect leaves websocket closed
  • Closed#27810[Bug]: QQBot closed WebSocket 导致无限空循环,单线程 CPU 100%
  • Closed#31101QQ Bot adapter: _read_events() silent loop after reconnect failure prevents further retries
  • Closed#31193QQ Bot Reconnect Busy Loop Causes 100% CPU Spin
  • Closed#31771[Bug]: QQBot adapter busy-loops after WebSocket reconnect failure, causing 100% CPU
  • Closed#42271fix(qqbot): listen_loop enters permanent dead state after network outage — _read_events() bypasses reconnect logic
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.14: 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

Subsystem
gateway
Merged PRs
#40574
Closed issues
6
Attempts
9 PRs closed without merging
Fix link checked
7 Jun 2026

For your agent

The same facts, typed.