Summary
When the TUI gateway starts, discover_mcp_tools() calls _run_stdio() for each MCP server using stdio transport. Inside _run_stdio() (an async def method), check_package_for_malware() makes a synchronous blocking urllib.request.urlopen() call to api.osv.dev. When the OSV API SSL handshake hangs (intermittent network issue), the entire asyncio event loop is frozen — blocking MCP discovery for up to 120s.
Meanwhile, the TUI's startup timeout is only 15s, so the user sees "gateway startup timeout" / "error: gateway exited" and the TUI becomes unusable.
Root Cause
File: tools/mcp_tool.py, line 1272, inside async def _run_stdio():
malware_error = check_package_for_malware(command, args)
check_package_for_malware → _query_osv → urllib.request.urlopen() is synchronous blocking I/O running inside the asyncio event loop thread. No await, no thread pool — it blocks the loop directly.
File: tools/osv_check.py, line 150:
with urllib.request.urlopen(req, timeout=_TIMEOUT) as resp:
Python's urllib timeout doesn't always cover the SSL handshake phase — the crash log confirms the thread is stuck in ssl.py:do_handshake().
Stack Trace (from ~/.hermes/logs/tui_gateway_crash.log)
=== SIGTERM received · 2026-05-20 14:55:12 ===
main-thread stack:
File "tui_gateway/entry.py", line 215, in main
discover_mcp_tools()
File "tools/mcp_tool.py", line 3312, in discover_mcp_tools
tool_names = register_mcp_servers(servers)
File "tools/mcp_tool.py", line 3262, in register_mcp_servers
_run_on_mcp_loop(_discover_all, timeout=120)
File "tools/mcp_tool.py", line 2208, in _run_on_mcp_loop
return future.result(timeout=wait_timeout)
(blocked waiting on future)
--- thread mcp-event-loop ---
File "tools/mcp_tool.py", line 1272, in _run_stdio
malware_error = check_package_for_malware(command, args)
File "tools/osv_check.py", line 47, in check_package_for_malware
malware = _query_osv(package, ecosystem, version)
File "tools/osv_check.py", line 150, in _query_osv
with urllib.request.urlopen(req, timeout=_TIMEOUT) as resp:
File "urllib/request.py", line 1348, in do_open
h.request(req.get_method(), req.selector, req.data, headers, ...)
File "http/client.py", line 1458, in connect
self.sock = self._context.wrap_socket(self.sock, ...)
File "ssl.py", line 1346, in do_handshake
self._sslobj.do_handshake()
Reproduction
- Configure multiple MCP servers with stdio transport (uvx/npx commands)
- Run
hermes --tui
- When
api.osv.dev has intermittent connectivity or SSL issues, the gateway hangs during startup
- After 15s, TUI shows "gateway startup timeout"
- After up to 120s, discovery times out → but TUI is already in broken state
The malware check itself is fast when the API is reachable (~0.00s measured). The issue is the intermittent SSL handshake hang with no async fallback.
Environment
- Hermes version:
v2026.5.16-587-g340d2b6de
- macOS 14.8.7, Python 3.11.4
- 5 MCP servers configured (fetch/uvx, filesystem/npx, github/npx, intellij/sse, time/uvx)
Suggested Fix
Use asyncio.to_thread() to run the malware check off the event loop:
# tools/mcp_tool.py, line 1272
malware_error = await asyncio.to_thread(check_package_for_malware, command, args)
Or alternatively, use an async HTTP client (aiohttp, httpx) inside _query_osv so the call is non-blocking.
Related
Summary
When the TUI gateway starts,
discover_mcp_tools()calls_run_stdio()for each MCP server using stdio transport. Inside_run_stdio()(anasync defmethod),check_package_for_malware()makes a synchronous blockingurllib.request.urlopen()call toapi.osv.dev. When the OSV API SSL handshake hangs (intermittent network issue), the entire asyncio event loop is frozen — blocking MCP discovery for up to 120s.Meanwhile, the TUI's startup timeout is only 15s, so the user sees "gateway startup timeout" / "error: gateway exited" and the TUI becomes unusable.
Root Cause
File:
tools/mcp_tool.py, line 1272, insideasync def _run_stdio():check_package_for_malware→_query_osv→urllib.request.urlopen()is synchronous blocking I/O running inside the asyncio event loop thread. Noawait, no thread pool — it blocks the loop directly.File:
tools/osv_check.py, line 150:Python's
urllibtimeout doesn't always cover the SSL handshake phase — the crash log confirms the thread is stuck inssl.py:do_handshake().Stack Trace (from
~/.hermes/logs/tui_gateway_crash.log)Reproduction
hermes --tuiapi.osv.devhas intermittent connectivity or SSL issues, the gateway hangs during startupThe malware check itself is fast when the API is reachable (~0.00s measured). The issue is the intermittent SSL handshake hang with no async fallback.
Environment
v2026.5.16-587-g340d2b6deSuggested Fix
Use
asyncio.to_thread()to run the malware check off the event loop:Or alternatively, use an async HTTP client (
aiohttp,httpx) inside_query_osvso the call is non-blocking.Related