hermes - 💡(How to fix) Fix [Bug] Mattermost cron job delivery fails with "Timeout context manager should be used inside a task"

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Error Message

delivery error: Plugin platform send failed: Timeout context manager should be used inside a task

Root Cause

This is the same class of bug as the Weixin platform issue (#17347, #13099, #12154) and the Hindsight plugin issue (#17226), but manifesting in the Mattermost adapter specifically.

The call chain:

cron scheduler scheduler.py:782-793
  → asyncio.run(_send_to_platform(...)) or ThreadPoolExecutor + asyncio.run(...)
    → _send_to_platform → _send_via_adapter
      → entry.standalone_sender_fn() which is _standalone_send()
        → aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=60))
          → RuntimeError: "Timeout context manager should be used inside a task"

Why it happens: The _standalone_send() function in plugins/platforms/mattermost/adapter.py creates an aiohttp.ClientSession with timeout=aiohttp.ClientTimeout(total=60). The aiohttp.ClientTimeout object is an async context manager that internally calls asyncio.current_task() to register a timeout. When _standalone_send() is called from the cron scheduler via asyncio.run() (potentially inside a ThreadPoolExecutor), the asyncio task context may not be properly set up, causing ClientTimeout.__aenter__ to raise RuntimeError("Timeout context manager should be used inside a task").

PR #3258 added explicit aiohttp.ClientTimeout(total=30) timeouts to all Mattermost HTTP calls, which introduced more places where this error can occur from the cron/direct-send path.

Fix Action

Workaround

No known workaround. The error occurs 100% of the time on Mattermost cron delivery. Interactive (gateway live-path) messages work fine since they run on the gateway's main event loop.

Code Example

delivery error: Plugin platform send failed: Timeout context manager should be used inside a task

---

cron scheduler scheduler.py:782-793
  → asyncio.run(_send_to_platform(...)) or ThreadPoolExecutor + asyncio.run(...)
    → _send_to_platform → _send_via_adapter
      → entry.standalone_sender_fn() which is _standalone_send()
        → aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=60))
RuntimeError: "Timeout context manager should be used inside a task"

---

--- a/plugins/platforms/mattermost/adapter.py
+++ b/plugins/platforms/mattermost/adapter.py
@@ -939,9 +939,15 @@ async def _standalone_send(
         from gateway.platforms.base import resolve_proxy_url, proxy_kwargs_for_aiohttp
         _proxy = resolve_proxy_url(platform_env_var="MATTERMOST_PROXY")
         _sess_kw, _req_kw = proxy_kwargs_for_aiohttp(_proxy)

-        async with aiohttp.ClientSession(
-            timeout=aiohttp.ClientTimeout(total=60),
-            **_sess_kw,
-        ) as session:
+        # NOTE: We deliberately pass *no* timeout kwarg to ClientSession.
+        # aiohttp.ClientTimeout(total=) creates an internal timeout context
+        # manager that calls asyncio.current_task(), which raises
+        # "Timeout context manager should be used inside a task" when this
+        # coroutine is executed via asyncio.run() inside a
+        # ThreadPoolExecutor (the cron scheduler delivery path).
+        # Instead we use asyncio.timeout(60)Python 3.11+ built-in
+        # async timeout — which doesn't rely on aiohttp's context manager.
+        async with aiohttp.ClientSession(**_sess_kw) as session:
+            async with asyncio.timeout(60):
                 # 1. Upload media (if any) and collect file_ids.
                 file_ids: List[str] = []
                 for media in media_files:
RAW_BUFFERClick to expand / collapse

Bug Description

When a Hermes cron job attempts to deliver output to Mattermost, the delivery consistently fails with:

delivery error: Plugin platform send failed: Timeout context manager should be used inside a task

The cron job itself executes successfully (last_status: ok) — only the final delivery step fails.

Environment

  • Hermes Agent: v0.12.0+
  • Platform: Mattermost (self-hosted)
  • Python: 3.11+
  • aiohttp: 3.11+
  • Trigger: Cron job delivery with deliver: "mattermost"

Steps to Reproduce

  1. Configure a Mattermost platform in ~/.hermes/config.yaml
  2. Create a cron job with deliver: "mattermost" targeting a channel
  3. Wait for the cron job to execute
  4. Observe last_delivery_error contains "Timeout context manager should be used inside a task"
  5. The message never arrives in Mattermost

Root Cause Analysis

This is the same class of bug as the Weixin platform issue (#17347, #13099, #12154) and the Hindsight plugin issue (#17226), but manifesting in the Mattermost adapter specifically.

The call chain:

cron scheduler scheduler.py:782-793
  → asyncio.run(_send_to_platform(...)) or ThreadPoolExecutor + asyncio.run(...)
    → _send_to_platform → _send_via_adapter
      → entry.standalone_sender_fn() which is _standalone_send()
        → aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=60))
          → RuntimeError: "Timeout context manager should be used inside a task"

Why it happens: The _standalone_send() function in plugins/platforms/mattermost/adapter.py creates an aiohttp.ClientSession with timeout=aiohttp.ClientTimeout(total=60). The aiohttp.ClientTimeout object is an async context manager that internally calls asyncio.current_task() to register a timeout. When _standalone_send() is called from the cron scheduler via asyncio.run() (potentially inside a ThreadPoolExecutor), the asyncio task context may not be properly set up, causing ClientTimeout.__aenter__ to raise RuntimeError("Timeout context manager should be used inside a task").

PR #3258 added explicit aiohttp.ClientTimeout(total=30) timeouts to all Mattermost HTTP calls, which introduced more places where this error can occur from the cron/direct-send path.

Why This Is Distinct from Existing Issues

IssuePlatformPathStatus
#17347Weixinsend_message toolOpen, P2
#17226Hindsight pluginaretain_batchOpen, P3
#20229WeChatcron deliveryDuplicate of #18014
#17595Weixinfile/image sendDuplicate of #17347
This issueMattermostcron deliveryNew

While the root cause is the same (aiohttp ClientTimeout used outside task context, or ClientSession reused across event loops), no existing issue covers the Mattermost adapter. The fix PRs open for Weixin (#19050, #17911, #18714) address the Weixin-specific send_weixin_direct() code path — they do not touch the Mattermost MattermostAdapter or _standalone_send().

Fix: Use asyncio.timeout() in _standalone_send (verified working)

The fix is in plugins/platforms/mattermost/adapter.py, function _standalone_send(). Replace the aiohttp.ClientSession timeout with Python 3.11+ built-in asyncio.timeout():

--- a/plugins/platforms/mattermost/adapter.py
+++ b/plugins/platforms/mattermost/adapter.py
@@ -939,9 +939,15 @@ async def _standalone_send(
         from gateway.platforms.base import resolve_proxy_url, proxy_kwargs_for_aiohttp
         _proxy = resolve_proxy_url(platform_env_var="MATTERMOST_PROXY")
         _sess_kw, _req_kw = proxy_kwargs_for_aiohttp(_proxy)

-        async with aiohttp.ClientSession(
-            timeout=aiohttp.ClientTimeout(total=60),
-            **_sess_kw,
-        ) as session:
+        # NOTE: We deliberately pass *no* timeout kwarg to ClientSession.
+        # aiohttp.ClientTimeout(total=…) creates an internal timeout context
+        # manager that calls asyncio.current_task(), which raises
+        # "Timeout context manager should be used inside a task" when this
+        # coroutine is executed via asyncio.run() inside a
+        # ThreadPoolExecutor (the cron scheduler delivery path).
+        # Instead we use asyncio.timeout(60) — Python 3.11+ built-in
+        # async timeout — which doesn't rely on aiohttp's context manager.
+        async with aiohttp.ClientSession(**_sess_kw) as session:
+            async with asyncio.timeout(60):
                 # 1. Upload media (if any) and collect file_ids.
                 file_ids: List[str] = []
                 for media in media_files:

Why this works:

  • asyncio.timeout(60) is a pure-Python async context manager (added in 3.11) that works correctly in any async context, including asyncio.run() inside a ThreadPoolExecutor
  • aiohttp.ClientTimeout() with no arguments creates an inactive timeout object (all fields None) that does not trigger the context manager check
  • Individual .post() and .get() calls within the session do not pass timeout= kwargs (they already didn't in _standalone_send), so only the session-level timeout and the asyncio.timeout(60) wrapper apply
  • The overall 60-second timeout protection is preserved via asyncio.timeout(60) wrapping the entire operation

Note: The MattermostAdapter class methods (_api_get, _api_post, _upload_file, etc.) use timeout=aiohttp.ClientTimeout(total=30) on self._session, but they always run on the gateway's own event loop where the context manager works correctly. Only _standalone_send() (the cron delivery path) needs this fix.

Workaround

No known workaround. The error occurs 100% of the time on Mattermost cron delivery. Interactive (gateway live-path) messages work fine since they run on the gateway's main event loop.

Affected Cron Jobs

Any Hermes cron job configured with deliver: "mattermost" will fail to deliver. This makes scheduled reports, notifications, and automated workflows via Mattermost completely non-functional.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING