hermes - ✅(Solved) Fix [Bug]: Home Assistant tool still uses per-call asyncio.run() path inside running event loops [1 pull requests, 1 participants]

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…
GitHub stats
NousResearch/hermes-agent#13586Fetched 2026-04-22 08:05:31
View on GitHub
Comments
0
Participants
1
Timeline
4
Reactions
0
Participants
Timeline (top)
labeled ×3cross-referenced ×1

Error Message

Error Output

ERROR asyncio: Unclosed client session ERROR asyncio: Unclosed connector

Fix Action

Fixed

PR fix notes

PR #13422: fix(tools): reuse persistent loop in Home Assistant async bridge

Description (problem / solution / changelog)

What does this PR do?

This PR fixes the remaining async-context gap in tools/homeassistant_tool.py's sync-to-async bridge.

The Home Assistant tool already reused persistent event loops for some sync call paths, but when _run_async() was invoked from inside an already-running event loop it still spawned a disposable thread and called asyncio.run(coro) per invocation. That per-call create-and-destroy loop behavior can leave aiohttp cleanup tied to dead loops, which risks Unclosed client session / Unclosed connector warnings in long-running Hermes processes.

This change makes that path reuse a dedicated persistent background loop thread instead, bringing the Home Assistant tool in line with Hermes' persistent-loop bridge pattern.

Related Issue

Fixes #13586

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✅ Tests (adding or improving test coverage)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • update tools/homeassistant_tool.py so async-context calls no longer use per-call asyncio.run()
  • add a dedicated persistent background loop thread for calls made while another event loop is already running
  • submit async-context coroutines with asyncio.run_coroutine_threadsafe(...)
  • keep the existing persistent main-thread and per-worker-thread loop behavior
  • add regression coverage in tests/tools/test_homeassistant_tool.py for loop reuse and liveness from inside a running event loop

How to Test

  1. Reproduce the bug scenario by calling tools/homeassistant_tool.py:_run_async() from within an already-running event loop.
  2. Verify that the call succeeds without falling back to per-call asyncio.run() loop creation/destruction behavior.
  3. Run the targeted regression tests:
    • python -m pytest tests/tools/test_homeassistant_tool.py -q -o 'addopts='
    • python -m pytest tests/gateway/test_homeassistant.py -q -o 'addopts='
  4. Optional broader check: run pytest tests/ -q.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Ubuntu 24.04

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Screenshots / Logs

Targeted local test results:

  • tests/tools/test_homeassistant_tool.py: passed
  • tests/gateway/test_homeassistant.py: passed

Note: I did not mark the full pytest tests/ -q checkbox as complete here because repository-wide baseline failures unrelated to this PR were observed separately.

Changed files

  • tests/tools/test_homeassistant_tool.py (modified, +118/-1)
  • tools/homeassistant_tool.py (modified, +90/-8)

Code Example

ERROR asyncio: Unclosed client session
ERROR asyncio: Unclosed connector

---

if loop and loop.is_running():
    import concurrent.futures
    with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
        future = pool.submit(asyncio.run, coro)
        return future.result(timeout=30)
RAW_BUFFERClick to expand / collapse

Bug Description

tools/homeassistant_tool.py reuses persistent event loops for some sync call paths, but _run_async() still falls back to a disposable-thread + asyncio.run(coro) path when called from inside an already-running event loop.

In long-running Hermes processes, that per-call create-and-destroy loop behavior can leave aiohttp cleanup tied to dead loops and risks Unclosed client session / Unclosed connector warnings.

Steps to Reproduce

  1. Start from current main.
  2. Call tools/homeassistant_tool.py:_run_async() from within an already-running event loop.
  3. Exercise a Home Assistant tool path that creates and cleans up an async HTTP client/session.
  4. Observe that this branch still creates a fresh event loop via asyncio.run() for each call instead of using a persistent loop.

Expected Behavior

When _run_async() is called from inside a running event loop, the Home Assistant tool should use a persistent async bridge loop so async client cleanup stays bound to a live event loop.

Actual Behavior

The async-context branch creates a disposable thread and runs asyncio.run(coro) per invocation, which creates and immediately closes a fresh event loop for that call.

Environment

  • OS: Ubuntu 24.04
  • Version/Commit: current main before PR #13422
  • Python version: 3.12
  • Browser (if applicable): N/A

Error Output

Observed risk/symptom in long-running processes:

ERROR asyncio: Unclosed client session
ERROR asyncio: Unclosed connector

Relevant problematic pattern:

if loop and loop.is_running():
    import concurrent.futures
    with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
        future = pool.submit(asyncio.run, coro)
        return future.result(timeout=30)

Additional Context

This issue is narrowly scoped to tools/homeassistant_tool.py.

It is related to async resource lifecycle management and the same general class of sync-to-async bridging problems seen elsewhere in Hermes, but I did not find an existing open issue that cleanly covers this exact Home Assistant tool path.

A proposed fix is in PR #13422: https://github.com/NousResearch/hermes-agent/pull/13422

extent analysis

TL;DR

The issue can be fixed by using a persistent async bridge loop in _run_async() when called from within an already-running event loop, instead of creating a fresh event loop via asyncio.run() per invocation.

Guidance

  • Identify the current event loop using asyncio.get_running_loop() and use it to run the coroutine instead of creating a new loop with asyncio.run().
  • Verify that the Unclosed client session and Unclosed connector warnings are resolved by checking the error output after applying the fix.
  • Review the proposed fix in PR #13422 to ensure it addresses the issue and does not introduce any new problems.
  • Consider testing the fix with different scenarios to ensure it works as expected in all cases.

Example

import asyncio

# Get the current running loop
loop = asyncio.get_running_loop()

# Use the current loop to run the coroutine
loop.run_until_complete(coro)

Notes

The proposed fix in PR #13422 should be reviewed and tested thoroughly to ensure it resolves the issue without introducing any new problems. The fix should be applied to the tools/homeassistant_tool.py file, specifically to the _run_async() function.

Recommendation

Apply the workaround by using a persistent async bridge loop in _run_async() when called from within an already-running event loop, as this should resolve the Unclosed client session and Unclosed connector warnings.

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

hermes - ✅(Solved) Fix [Bug]: Home Assistant tool still uses per-call asyncio.run() path inside running event loops [1 pull requests, 1 participants]