Skip to content

fix(qqbot): accept is_reconnect kwarg in QQAdapter.connect() - #59429

Closed
lemonwan wants to merge 1 commit into
NousResearch:mainfrom
lemonwan:fix/qqbot-is-reconnect-kwarg
Closed

fix(qqbot): accept is_reconnect kwarg in QQAdapter.connect()#59429
lemonwan wants to merge 1 commit into
NousResearch:mainfrom
lemonwan:fix/qqbot-is-reconnect-kwarg

Conversation

@lemonwan

@lemonwan lemonwan commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Symptom

After the QQ WebSocket receives its first code=4009 Session timed out close (which happens routinely, ~every 30 min per QQ's own docs), the gateway reconnect loop dies with:

ERROR gateway.run: ✗ qqbot error: QQAdapter.connect() got an unexpected keyword argument 'is_reconnect'
WARNING gateway.run: Reconnect qqbot error: QQAdapter.connect() got an unexpected keyword argument 'is_reconnect', next retry in 120s

…and never recovers. QQ silently stops delivering messages to the agent until the gateway is manually restarted (and even then, the next 4009 close kills it again). Nothing on the QQ side surfaces the failure — users just notice their agent has stopped responding on QQ.

Root cause

gateway/run.py forwards is_reconnect=True to adapter.connect() on every retry (line 7724 → _call_adapter_connect at 3383). All ~30 other adapters — Telegram, Discord, Slack, Feishu, WhatsApp, Signal, WeChat, Matrix, Teams, LINE, Email, IRC, SMS, DingTalk, WeCom, Homeassistant, Google Chat, Mattermost, Ntfy, Photon, Raft, Simplex, Yuanbao, API Server, BlueBubbles, Webhook, MSGraph Webhook, Relay — accept *, is_reconnect: bool = False per the BasePlatformAdapter.connect() contract (see gateway/platforms/base.py:2864).

QQAdapter.connect() was the lone holdout with a bare async def connect(self) -> bool: signature.

Fix

Add the *, is_reconnect: bool = False kwarg to QQAdapter.connect(). QQ's connection flow does not need to branch on cold-boot vs. reconnect today (unlike Telegram, which uses it to drive drop_pending_updates), so the parameter is accepted-and-ignored with a del is_reconnect and a docstring pointer explaining why it must nonetheless be accepted.

Verification

On the affected host, before this patch: qqbot cycled through 30-min timeout → TypeError → 60s → 120s retry backoff, permanently disconnected.

After this patch: systemctl --user restart hermes-gateway → qqbot connects → user confirmed message delivery restored end-to-end.

Notes

  • One-line contract fix, no behavior change beyond accepting the kwarg.
  • Consistent with the existing pattern used by every other adapter in the tree.
  • Would recommend a follow-up test that iterates every adapter class and asserts connect accepts is_reconnect to catch regressions of this exact class, but out of scope for this fix.

The gateway's reconnect loop forwards is_reconnect=True to every
adapter.connect() call (gateway/run.py:7724). QQAdapter.connect()
was the only adapter that didn't accept the kwarg, so every retry
after a websocket drop raised:

    TypeError: QQAdapter.connect() got an unexpected keyword
               argument 'is_reconnect'

...leaving qqbot permanently disconnected after the first 4009
'Session timed out' close. This aligns QQ with the BasePlatformAdapter
contract that all ~30 other adapters already implement.
@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery platform/qqbot QQ Bot adapter P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages duplicate This issue or pull request already exists labels Jul 6, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #52966 — the earliest still-open canonical fix for the QQBot is_reconnect regression (issue #52914). Identical one-line QQAdapter.connect(self, *, is_reconnect: bool = False) signature fix. (The earlier #52922 is closed/unmerged, so it is not the dup anchor.) Saturated cluster; flagging for a human to pick and merge one.

teknium1 pushed a commit that referenced this pull request Jul 10, 2026
…onnect

The gateway reconnect watcher forwards is_reconnect=True to every
adapter.connect() call on every retry. Adapters whose signature omits
the kwarg raise TypeError at every reconnect attempt and stay silently
disconnected — the exact bug that shipped for QQAdapter and only
surfaced after messages stopped flowing on the QQ channel for hours.

This test statically parses every adapter.py under gateway/platforms/
and plugins/platforms/ (via AST, so third-party SDKs like slack_sdk,
matrix-nio, aiohttp, telegram, etc. are NOT required in the test env)
and asserts every *Adapter class with an async connect() accepts
is_reconnect — either as a keyword-only argument or absorbed by
**kwargs.

Also fixes plugins/platforms/wecom/callback_adapter.py:WecomCallbackAdapter,
which the new test caught as a second offender. Same class of bug: bare
'async def connect(self)' signature would die on the first reconnect.

Companion to #59429 (which fixed the original QQAdapter offender).
santhreal pushed a commit to santhreal/hermes-agent that referenced this pull request Jul 13, 2026
…onnect

The gateway reconnect watcher forwards is_reconnect=True to every
adapter.connect() call on every retry. Adapters whose signature omits
the kwarg raise TypeError at every reconnect attempt and stay silently
disconnected — the exact bug that shipped for QQAdapter and only
surfaced after messages stopped flowing on the QQ channel for hours.

This test statically parses every adapter.py under gateway/platforms/
and plugins/platforms/ (via AST, so third-party SDKs like slack_sdk,
matrix-nio, aiohttp, telegram, etc. are NOT required in the test env)
and asserts every *Adapter class with an async connect() accepts
is_reconnect — either as a keyword-only argument or absorbed by
**kwargs.

Also fixes plugins/platforms/wecom/callback_adapter.py:WecomCallbackAdapter,
which the new test caught as a second offender. Same class of bug: bare
'async def connect(self)' signature would die on the first reconnect.

Companion to NousResearch#59429 (which fixed the original QQAdapter offender).
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused QQBot reconnect fix. This is already implemented on current main.

Automated hermes-sweeper review evidence:

  • gateway/platforms/qqbot/adapter.py:281 already defines QQAdapter.connect(self, *, is_reconnect: bool = False).
  • Commit 276542c729c10ff9d093760897f4c2d1256a79ce added that production fix and is an ancestor of current main.
  • tests/gateway/test_qqbot.py:193 covers both cold and explicit reconnect calls.
  • The later repository-wide contract regression test is present at tests/gateway/test_adapter_connect_is_reconnect_contract.py:126 (commit 0f8603c571beb6c944bcdb9fa2d45f8f85aaa750).

The member comment correctly identified the duplicate cluster; this PR's requested guarantee is now covered on main.

@teknium1 teknium1 closed this Jul 15, 2026
@teknium1 teknium1 added the sweeper:implemented-on-main Sweeper: behavior already present on current main label Jul 15, 2026
justemu pushed a commit to justemu/hermes-agent that referenced this pull request Jul 18, 2026
…onnect

The gateway reconnect watcher forwards is_reconnect=True to every
adapter.connect() call on every retry. Adapters whose signature omits
the kwarg raise TypeError at every reconnect attempt and stay silently
disconnected — the exact bug that shipped for QQAdapter and only
surfaced after messages stopped flowing on the QQ channel for hours.

This test statically parses every adapter.py under gateway/platforms/
and plugins/platforms/ (via AST, so third-party SDKs like slack_sdk,
matrix-nio, aiohttp, telegram, etc. are NOT required in the test env)
and asserts every *Adapter class with an async connect() accepts
is_reconnect — either as a keyword-only argument or absorbed by
**kwargs.

Also fixes plugins/platforms/wecom/callback_adapter.py:WecomCallbackAdapter,
which the new test caught as a second offender. Same class of bug: bare
'async def connect(self)' signature would die on the first reconnect.

Companion to NousResearch#59429 (which fixed the original QQAdapter offender).
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…onnect

The gateway reconnect watcher forwards is_reconnect=True to every
adapter.connect() call on every retry. Adapters whose signature omits
the kwarg raise TypeError at every reconnect attempt and stay silently
disconnected — the exact bug that shipped for QQAdapter and only
surfaced after messages stopped flowing on the QQ channel for hours.

This test statically parses every adapter.py under gateway/platforms/
and plugins/platforms/ (via AST, so third-party SDKs like slack_sdk,
matrix-nio, aiohttp, telegram, etc. are NOT required in the test env)
and asserts every *Adapter class with an async connect() accepts
is_reconnect — either as a keyword-only argument or absorbed by
**kwargs.

Also fixes plugins/platforms/wecom/callback_adapter.py:WecomCallbackAdapter,
which the new test caught as a second offender. Same class of bug: bare
'async def connect(self)' signature would die on the first reconnect.

Companion to NousResearch#59429 (which fixed the original QQAdapter offender).
leewenjie pushed a commit to leewenjie/hermes-agent that referenced this pull request Aug 7, 2026
…onnect

The gateway reconnect watcher forwards is_reconnect=True to every
adapter.connect() call on every retry. Adapters whose signature omits
the kwarg raise TypeError at every reconnect attempt and stay silently
disconnected — the exact bug that shipped for QQAdapter and only
surfaced after messages stopped flowing on the QQ channel for hours.

This test statically parses every adapter.py under gateway/platforms/
and plugins/platforms/ (via AST, so third-party SDKs like slack_sdk,
matrix-nio, aiohttp, telegram, etc. are NOT required in the test env)
and asserts every *Adapter class with an async connect() accepts
is_reconnect — either as a keyword-only argument or absorbed by
**kwargs.

Also fixes plugins/platforms/wecom/callback_adapter.py:WecomCallbackAdapter,
which the new test caught as a second offender. Same class of bug: bare
'async def connect(self)' signature would die on the first reconnect.

Companion to NousResearch#59429 (which fixed the original QQAdapter offender).
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…onnect

The gateway reconnect watcher forwards is_reconnect=True to every
adapter.connect() call on every retry. Adapters whose signature omits
the kwarg raise TypeError at every reconnect attempt and stay silently
disconnected — the exact bug that shipped for QQAdapter and only
surfaced after messages stopped flowing on the QQ channel for hours.

This test statically parses every adapter.py under gateway/platforms/
and plugins/platforms/ (via AST, so third-party SDKs like slack_sdk,
matrix-nio, aiohttp, telegram, etc. are NOT required in the test env)
and asserts every *Adapter class with an async connect() accepts
is_reconnect — either as a keyword-only argument or absorbed by
**kwargs.

Also fixes plugins/platforms/wecom/callback_adapter.py:WecomCallbackAdapter,
which the new test caught as a second offender. Same class of bug: bare
'async def connect(self)' signature would die on the first reconnect.

Companion to NousResearch#59429 (which fixed the original QQAdapter offender).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery duplicate This issue or pull request already exists P2 Medium — degraded but workaround exists platform/qqbot QQ Bot adapter sweeper:implemented-on-main Sweeper: behavior already present on current main sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants