Skip to content

[Bug] send_message tool fails with Weixin: Timeout context manager should be used inside a task #13281

@AngleNaris

Description

@AngleNaris

Bug: send_message tool fails with Weixin - Timeout context manager should be used inside a task

Repository: https://github.com/NousResearch/hermes-agent


Description

Calling the send_message tool with the weixin platform target fails with:

Weixin send failed: Timeout context manager should be used inside a task

This happens on both text and media messages (including MEDIA:/path/to/file uploads). The error originates from aiohttp/helpers.py:

# aiohttp/helpers.py, line 678
def __enter__(self) -> BaseTimerContext:
    task = asyncio.current_task(loop=self._loop)
    if task is None:
        raise RuntimeError("Timeout context manager should be used inside a task")

The same underlying issue causes text_to_speech to fail with:

TTS generation failed (edge): Connection timeout to host wss://speech.platform.bing.com/...

Environment

  • Hermes Agent: NousResearch/hermes-agent (latest)
  • Python: 3.11
  • aiohttp: 3.13.5
  • Platform: Weixin (WeChat) via send_message tool
  • Session: Running inside the CLI/gateway with an existing event loop

Root Cause Analysis

Trigger path:

  1. send_message_tool._handle_send() -> model_tools._run_async(_send_to_platform(...))
  2. _run_async detects asyncio.get_running_loop().is_running() == True (gateway/CLI loop is already running)
  3. Falls into the thread pool path: ThreadPoolExecutor(max_workers=1).submit(asyncio.run, coro)
  4. asyncio.run() creates a fresh event loop in the worker thread
  5. Inside _send_weixin() -> send_weixin_direct() -> async with aiohttp.ClientSession(...) as session (line 2010 of gateway/platforms/weixin.py)
  6. ClientSession.aenter creates asyncio.timeout(self._timeout) (aiohttp >= 3.9)
  7. asyncio.timeout.enter calls asyncio.current_task(loop=self._loop) which returns None because the Timeout object was initialized with a loop reference that does not match the actual running loop in the worker thread.

Known aiohttp issues:

Reproduction Steps

# In a Hermes session with Weixin configured:
send_message(target="weixin", message="test")
# OR
send_message(target="weixin:o9cq80zzgXQ1kJhIVQTWD36hui1k@im.wechat", message="MEDIA:/tmp/test.png")

Expected Behavior

Message is sent successfully to Weixin/WeChat.

Actual Behavior

Weixin send failed: Timeout context manager should be used inside a task

Suggested Fix

The issue is that asyncio.run() in _run_async's thread pool path creates a new loop, but the ClientSession initialization in send_weixin_direct may be receiving a stale loop reference.

  1. For send_weixin_direct: Ensure the ClientSession is created without relying on implicit loop binding, or use explicit loop handling with aiohttp.TCPConnector.

  2. For _run_async: Consider switching the thread pool path to use loop.run_until_complete(coro) on a persistent per-thread loop (matching the non-worker-thread path) instead of asyncio.run(), to avoid the loop reference mismatch.

  3. Alternative: The _run_async docstring mentions When called from a worker thread... use a per-thread persistent loop. The worker thread path already has this (_get_worker_loop().run_until_complete(coro)). Consider whether the main thread path (tool_loop.run_until_complete(coro)) should be used more broadly when the running loop is in the main thread but we are calling from a sync tool handler.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existscomp/gatewayGateway runner, session dispatch, deliverycomp/toolsTool registry, model_tools, toolsetsplatform/wecomWeCom / WeChat Work adaptertool/ttsText-to-speech and transcriptiontype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions