c0ff25a1f8
Pattern-A architectural fix: blocking calls inside async functions freeze the gateway/uvicorn event loop for every adapter, timer, and health check. Known incidents: 17-minute getaddrinfo freeze (#91912 class), 10s restart freeze in start_gateway (#36163). Fixes at the four unguarded core sites: - gateway/platforms/webhook.py: `gh pr comment` subprocess (30s timeout) now runs via asyncio.to_thread — a webhook delivery no longer freezes every other platform for the duration of a network call. - gateway/run.py start_gateway --replace: two time.sleep() waits (10s + 5s worst case) become await asyncio.sleep() (re-lands #36163 at current line numbers, credit AhmetArif0). - gateway/slash_commands.py /save: session render + file write move off the loop (scales with transcript size). - hermes_cli/web_server.py voice TTS: multi-MB audio file read + unlink move off the loop. Prevention gate so the bug class cannot re-enter: - pyproject.toml [tool.ruff.lint] select gains ASYNC210/220/221/251 (blocking HTTP / Popen / subprocess.run / time.sleep in async def). These run in the existing blocking `ruff check .` CI job. - Frozen ratchet baseline in per-file-ignores for the remaining legacy sites (detached restart watchers; router sweep in flight via #84376; two platform adapters), each documented for burn-down. New files or new violations fail CI immediately. - tests/** keeps the relaxation (deliberate sleeps in fixtures). Verification: - ruff check . green on this branch; sabotage file with time.sleep + subprocess.run in async def fails the gate with 2 errors. - New behavioral test test_webhook_offloop_delivery.py asserts loop liveness DURING delivery (ticker coroutine): 1 tick on the old blocking code (fails), 21 ticks off-loop (passes). - 50 webhook/replace gateway tests + 26 save/export tests pass. Co-authored-by: AhmetArif0 <147827411+AhmetArif0@users.noreply.github.com>