Skip to content

fix: weixin send crosses event loops when called from tool handlers - #17319

Closed
zdev0x wants to merge 1 commit into
NousResearch:mainfrom
zdev0x:fix/weixin-send-cross-loop-session
Closed

fix: weixin send crosses event loops when called from tool handlers#17319
zdev0x wants to merge 1 commit into
NousResearch:mainfrom
zdev0x:fix/weixin-send-cross-loop-session

Conversation

@zdev0x

@zdev0x zdev0x commented Apr 29, 2026

Copy link
Copy Markdown

Problem

send_weixin_direct() reused the live adapter's aiohttp.ClientSession, but _run_async() spawns a fresh thread + event loop for async tool handlers in the gateway context. aiohttp sessions cannot be used across event loops, causing:

Timeout context manager should be used inside a task

This makes the send_message tool fail every time it tries to send via Weixin, while the gateway is running.

Root Cause

When the gateway is running, send_weixin_direct() takes Path 1 (reuse live adapter session) because _LIVE_ADAPTERS has the adapter registered. But _run_async() runs the coroutine in a new thread with asyncio.run(), creating a new event loop. The aiohttp session from the gateway's loop can't be used there.

Fix

Detect the event loop mismatch via poll_task.get_loop() and fall back to a standalone session (Path 2) when the loops differ.

``\python

Before

if live_adapter is not None and send_session is not None and not send_session.closed:

After

can_reuse = (
live_adapter is not None
and send_session is not None
and not send_session.closed
)
if can_reuse and live_adapter._poll_task is not None:
try:
task_loop = live_adapter._poll_task.get_loop()
except AttributeError:
task_loop = None # Python <3.9 — fall through to safe path
if task_loop is not asyncio.get_running_loop():
can_reuse = False
if can_reuse:


## Tests

Added 2 new tests in `TestWeixinDirectSendCrossLoop`:
- `test_falls_back_to_new_session_when_no_live_adapter` — no live adapter → Path 2
- `test_falls_back_to_new_session_on_loop_mismatch` — live adapter in different loop → Path 2

All 44 tests pass (42 existing + 2 new).

send_weixin_direct() reused the live adapter's aiohttp session, but
_run_async() spawns a fresh thread+loop for async tool handlers in the
gateway context.  aiohttp sessions cannot be used across event loops,
causing 'Timeout context manager should be used inside a task'.

Detect the loop mismatch via poll_task.get_loop() and fall back to a
standalone session when the loops differ.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery platform/wecom WeCom / WeChat Work adapter labels Apr 29, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Likely duplicate of #13350 — same root cause: cross-event-loop aiohttp ClientSession reuse in send_weixin_direct(). See also #14873, #15911, #16790.

@zdev0x

zdev0x commented Apr 29, 2026

Copy link
Copy Markdown
Author

Closing as duplicate of #13350 (and #14873, #15911, #16790). Same root cause: cross-event-loop aiohttp ClientSession reuse in send_weixin_direct(). Verified fix locally — happy to help test any of the open PRs.

@zdev0x zdev0x closed this Apr 29, 2026
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 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.

2 participants