Skip to content

fix(weixin): skip live adapter reuse in send_weixin_direct when on a different event loop#12810

Open
yueyefengs wants to merge 1 commit into
NousResearch:mainfrom
yueyefengs:fix/weixin-cross-loop-timeout
Open

fix(weixin): skip live adapter reuse in send_weixin_direct when on a different event loop#12810
yueyefengs wants to merge 1 commit into
NousResearch:mainfrom
yueyefengs:fix/weixin-cross-loop-timeout

Conversation

@yueyefengs

Copy link
Copy Markdown

Problem

When the cron scheduler's live adapter delivery fails (e.g. ret=-2 network error), it falls back to a standalone path that runs _send_to_platform inside asyncio.run() in a thread-pool executor — a fresh event loop.

send_weixin_direct then finds the live adapter in _LIVE_ADAPTERS and unconditionally reuses its aiohttp.ClientSession, which was created on the gateway event loop. Using that session from a different loop causes aiohttp's internal asyncio.timeout() to raise:

Timeout context manager should be used inside a task

because asyncio.timeout() (Python 3.11+) checks asyncio.current_task(), and while a Task does exist on the new loop, the connector is bound to the old one — the mismatch triggers the assertion.

The result is three retried failures followed by a delivery error log, and the cron message never reaches the user.

Fix

Before reusing the live adapter session in send_weixin_direct, compare connector._loop against asyncio.get_running_loop(). If they differ, skip the live adapter branch and fall through to the existing fresh-session path (async with aiohttp.ClientSession(...)).

The live adapter fast-path is preserved for all callers that are already on the correct loop (e.g. the /send_message tool running inside the gateway).

Test Plan

  • pytest tests/gateway/test_weixin.py — 42 passed, 0 failures
  • Reproduces via: cron job delivery where first live-adapter attempt returns ret=-2, triggering the standalone fallback path

…reuse

The previous attempt used connector._loop to detect cross-loop reuse, but
aiohttp 3.9+ no longer stores _loop on the connector, so the guard always
evaluated to True and the bug persisted.

Instead, store asyncio.get_running_loop() as self._event_loop in connect(),
then compare it against the caller's running loop in send_weixin_direct()
before deciding whether to reuse the live adapter's session.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@yueyefengs yueyefengs force-pushed the fix/weixin-cross-loop-timeout branch from 2b533a4 to bf020c2 Compare April 20, 2026 04:56
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists platform/wecom WeCom / WeChat Work adapter comp/gateway Gateway runner, session dispatch, delivery comp/cron Cron scheduler and job management labels Apr 22, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Likely duplicate of #13350 and #13520 — all three fix cross-event-loop aiohttp session reuse in Weixin direct send.

1 similar comment
@alt-glitch

Copy link
Copy Markdown
Collaborator

Likely duplicate of #13350 and #13520 — all three fix cross-event-loop aiohttp session reuse in Weixin direct send.

@u9wangcl

u9wangcl commented May 1, 2026

Copy link
Copy Markdown

Confirming reproduction and verifying this fix shape on recent main:

  • Version: v0.12.0 (commit 0159f25)
  • Python: 3.11.15, aiohttp 3.13.5
  • Platform: weixin (DM)
  • Trigger: agent calls send_message tool with MEDIA:/tmp/foo.pdf while the gateway has the live adapter registered. 100% reproducible.

I applied a functionally-equivalent local patch to send_weixin_direct (loop-affinity check on the live adapter's session before reusing it, fall through to the existing fresh-ClientSession branch otherwise) and the PDF was delivered successfully via the agent tool path on the first try. Text sends were never affected; the existing fresh-session fallback path worked as-is once the cross-loop branch was bypassed.

Minor naming nit on this PR: caching self._event_loop in connect() avoids reaching into aiohttp's private session._loop, which is the more forward-compatible choice — happy to see it land.

For the maintainers triaging the cluster of similar PRs (#13520, #14530, #14873, #15911, #16790, #17267, #17557): all that I've read converge on the same one-liner — gate the live-adapter reuse on loop affinity. This PR is the earliest and one of the simpler diffs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cron Cron scheduler and job management comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/wecom WeCom / WeChat Work adapter type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants