Skip to content

fix(gateway): recover WeCom sends after lost subscription - #51801

Open
Jeven35 wants to merge 1 commit into
NousResearch:mainfrom
Jeven35:fix/wecom-lost-subscription
Open

Jeven35 wants to merge 1 commit into
NousResearch:mainfrom
Jeven35:fix/wecom-lost-subscription

Conversation

@Jeven35

@Jeven35 Jeven35 commented Jun 24, 2026

Copy link
Copy Markdown

Summary

  • reconnect and retry once when WeCom sends hit errcode 846609 / not subscribed
  • cache inbound file-wrapped image bytes as images so quoted .bin images remain visible to the agent
  • add WeCom regression tests for lost-subscription retry and file-wrapped image caching

Testing

  • python3 -m compileall plugins/platforms/wecom/adapter.py tests/gateway/test_wecom.py
  • manual async smoke test for the new WeCom retry and image-cache paths

Note: full pytest was not run locally because pytest was not installed in the local Python environment.

@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery platform/wecom WeCom / WeChat Work adapter P3 Low — cosmetic, nice to have sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Jun 24, 2026
@alt-glitch

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Related: this joins the WeCom errcode 846609 silent-delivery cluster around issue #29667 (also #47564). Existing open fix PRs taking the reconnect-retry approach: #24790 (earliest), #37257, #31393; #44632 prevents duplicate gateway instances. This PR adds file-wrapped image caching on top of the 846609 reconnect-retry, so it is broader in scope than the pure-846609 PRs. Flagging the cluster for a maintainer to pick the canonical fix — not auto-closing any.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the documented WeCom 846609 delivery gap. Current main still returns failures from send() without a reconnect (plugins/platforms/wecom/adapter.py:1415-1423), so the premise is valid; the related #29667/#47564 discussion is relevant.

Problems

  • The new retry is only reached from markdown send() (plugins/platforms/wecom/adapter.py:1408-1465 in the PR). Media delivery still runs through _send_media_source() and returns errors from its upload/media operations (plugins/platforms/wecom/adapter.py:1203-1355 on current main), without this recovery.
  • _recover_lost_subscription() calls disconnect() on shared adapter state (plugins/platforms/wecom/adapter.py:1067 in the PR). Current disconnect() cancels shared listener/heartbeat tasks, fails pending responses, and closes the socket/session (plugins/platforms/wecom/adapter.py:241-267), but the PR has no lock to prevent concurrent send recoveries from interleaving.

Suggested changes

  • Use a lock-guarded, once-only recovery helper shared by markdown and media outbound paths; recheck connection health after acquiring the lock.
  • Add media and concurrent-recovery tests rather than mocking only independent disconnect() / connect() calls.

Automated hermes-sweeper review.

reason,
)
try:
await self.disconnect()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

disconnect() cancels shared listener/heartbeat tasks, fails all pending response futures, and closes the shared socket/session. Guard this recovery with an asyncio.Lock and recheck whether another sender already restored the subscription after acquiring it; concurrent 846609 sends can otherwise interleave lifecycle operations.

return SendResult(success=False, error="chat_id is required")

try:
async def _send_once() -> Dict[str, Any]:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This recovery path only wraps markdown send(). Native media uses _send_media_source() plus upload and media-send helpers, which remain unmodified and still return 846609 failures without reconnecting. Please route all outbound WeCom paths through the same serialized recovery policy.

@rihaku899

Copy link
Copy Markdown

Verification update against current main (9aa7530f7b53699e2c6d648ded8f6300503b3dc7, checked 2026-08-26).

The current adapter path is plugins/platforms/wecom/adapter.py. A focused 20-test regression suite covering lost-subscription recovery, proactive/reply/media send paths, generation coordination, subscription-ready waiting, stale group reply request IDs, heartbeat failure handling, and adapter lock lifecycle:

  • fails against the unmodified current main (2 failures and 20 errors);
  • passes 20/20 when a generation-aware recovery patch is applied on top of the same current main;
  • the patch applies without conflicts.

The candidate fix is broader than a single reconnect-and-retry call:

  • treats 846609 as a generation-scoped subscription recovery event;
  • coalesces concurrent recovery so only one new connection is opened;
  • waits for the new subscription-ready generation instead of using a fixed delay;
  • prevents an old group reply request ID from being replayed after a generation switch;
  • routes proactive, reply, and media send transactions through the same recovery path.

This appears to be the current-path continuation of #29667/#47564. Please reconcile this PR with the current plugins/platforms/wecom/adapter.py path rather than opening another issue for the same lost-subscription failure.

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 P3 Low — cosmetic, nice to have platform/wecom WeCom / WeChat Work adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform 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.

4 participants