{"repo":"NousResearch/hermes-agent","public_id":"2138","display_label":"Cron asyncio coroutine cleanup on RuntimeError","summary":"These items address a bug in the cron component where a coroutine is left unawaited or abandoned when asyncio.run() raises a RuntimeError. The fixes focus on properly closing that coroutine to avoid resource leaks.","subsystem":"cron","counts":{"members":5,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":3},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":2317},"resolution":{"state":"resolved","by":"pr-2317"},"freshness":{"state":"stale","last_verified_at":"2026-03-21T14:21:19+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.4},"fix_edges":[{"issue_number":2138,"pr_number":2317,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":2138,"title":"fix(cron): unawaited coroutine when asyncio.run() hits RuntimeError","url":null,"state":"closed"},{"kind":"pr","number":2142,"title":"fix(cron): close unawaited coroutine when asyncio.run() hits RuntimeError","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":2248,"title":"fix(cron): close unawaited coroutine when asyncio.run() hits RuntimeError","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":2290,"title":"fix(cron): close abandoned coroutine when asyncio.run() raises RuntimeError","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#2317","number":2317}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":2317,"title":"fix(cron): close abandoned coroutine when asyncio.run() raises RuntimeError","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["cron/scheduler.py"],"fix_hunks":[{"file":"cron/scheduler.py","hunks":["@@ -170,11 +170,15 @@ def _deliver_result(job: dict, content: str) -> None:\n     )\n \n     # Run the async send in a fresh event loop (safe from any thread)\n+    coro = _send_to_platform(platform, pconfig, chat_id, wrapped, thread_id=thread_id)\n     try:\n-        result = asyncio.run(_send_to_platform(platform, pconfig, chat_id, wrapped, thread_id=thread_id))\n+        result = asyncio.run(coro)\n     except RuntimeError:\n-        # asyncio.run() fails if there's already a running loop in this thread;\n-        # spin up a new thread to avoid that.\n+        # asyncio.run() checks for a running loop before awaiting the coroutine;\n+        # when it raises, the original coro was never started — close it to\n+        # prevent \"coroutine was never awaited\" RuntimeWarning, then retry in a\n+        # fresh thread that has no running loop.\n+        coro.close()\n         import concurrent.futures\n         with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:\n             future = pool.submit(asyncio.run, _send_to_platform(platform, pconfig, chat_id, wrapped, thread_id=thread_id))\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}