Summary
When an HTTP MCP server (StreamableHTTP transport) is configured and the session ends, the following error is printed to stderr:
Unhandled exception in event loop:
File ".../httpx/_client.py", line 1985, in aclose
await self._transport.aclose()
...
File ".../asyncio/base_events.py", line 762, in call_soon
self._check_closed()
File ".../asyncio/base_events.py", line 520, in _check_closed
raise RuntimeError('Event loop is closed')
Exception Event loop is closed
Root Cause
When _stop_mcp_loop() stops and closes the MCP background event loop, the httpx/httpcore async transports held by the MCP SDK's streamablehttp_client eventually fire __del__ finalizers. These finalizers call call_soon() on the now-dead loop to schedule connection teardown. asyncio catches the resulting RuntimeError and routes it to the loop's exception handler, which by default prints the error to stderr.
The connection teardown is a no-op at this point (the loop is gone) — the error is purely cosmetic noise.
Steps to Reproduce
- Configure an HTTP MCP server in
~/.hermes/config.yaml
- Start a session (the MCP server connects via StreamableHTTP)
- Exit the session
- Observe
Unhandled exception in event loop: Event loop is closed on stderr
Impact
Cosmetic only — no data loss, no broken functionality. However it is alarming and may mask real errors in output.
Related
Same class of async shutdown race previously fixed in:
model_tools.py (ab6abc2) — persistent event loop for tool handlers
agent/auxiliary_client.py — stale async client cache invalidation
Summary
When an HTTP MCP server (StreamableHTTP transport) is configured and the session ends, the following error is printed to stderr:
Root Cause
When
_stop_mcp_loop()stops and closes the MCP background event loop, the httpx/httpcore async transports held by the MCP SDK'sstreamablehttp_clienteventually fire__del__finalizers. These finalizers callcall_soon()on the now-dead loop to schedule connection teardown. asyncio catches the resultingRuntimeErrorand routes it to the loop's exception handler, which by default prints the error to stderr.The connection teardown is a no-op at this point (the loop is gone) — the error is purely cosmetic noise.
Steps to Reproduce
~/.hermes/config.yamlUnhandled exception in event loop: Event loop is closedon stderrImpact
Cosmetic only — no data loss, no broken functionality. However it is alarming and may mask real errors in output.
Related
Same class of async shutdown race previously fixed in:
model_tools.py(ab6abc2) — persistent event loop for tool handlersagent/auxiliary_client.py— stale async client cache invalidation