From 1cd499b4a57cc04660ff270b0f69366b90d4ea81 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 8 Oct 2025 13:42:46 +0200 Subject: [PATCH] Fix WebSocket transport None race condition in proxy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a transport validity check before WebSocket upgrade to prevent AssertionError when clients disconnect during handshake. The issue occurs when a client connection is lost between the API state check and server.prepare() call, causing request.transport to become None and triggering "assert transport is not None" in aiohttp's _pre_start(). The fix detects the closed connection early and raises HTTPBadRequest with a clear reason instead of crashing with an AssertionError. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- supervisor/api/proxy.py | 5 +++++ tests/api/test_proxy.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/supervisor/api/proxy.py b/supervisor/api/proxy.py index 01078625d..2d7ff49c8 100644 --- a/supervisor/api/proxy.py +++ b/supervisor/api/proxy.py @@ -222,6 +222,11 @@ class APIProxy(CoreSysAttributes): raise HTTPBadGateway() _LOGGER.info("Home Assistant WebSocket API request initialize") + # Check if transport is still valid before WebSocket upgrade + if request.transport is None: + _LOGGER.warning("WebSocket connection lost before upgrade") + raise web.HTTPBadRequest(reason="Connection closed") + # init server server = web.WebSocketResponse(heartbeat=30) await server.prepare(request) diff --git a/tests/api/test_proxy.py b/tests/api/test_proxy.py index 9694e4f3f..c598d50cf 100644 --- a/tests/api/test_proxy.py +++ b/tests/api/test_proxy.py @@ -223,6 +223,34 @@ async def test_proxy_auth_abort_log( ) +async def test_websocket_transport_none( + coresys, + caplog: pytest.LogCaptureFixture, +): + """Test WebSocket connection with transport None is handled gracefully.""" + from aiohttp import web + + # Get the API proxy instance from coresys + api_proxy = APIProxy.__new__(APIProxy) + api_proxy.coresys = coresys + + # Create a mock request with transport set to None to simulate connection loss + mock_request = AsyncMock(spec=web.Request) + mock_request.transport = None + + caplog.clear() + with caplog.at_level(logging.WARNING): + # This should raise HTTPBadRequest, not AssertionError + with pytest.raises(web.HTTPBadRequest) as exc_info: + await api_proxy.websocket(mock_request) + + # Verify the error reason + assert exc_info.value.reason == "Connection closed" + + # Verify the warning was logged + assert "WebSocket connection lost before upgrade" in caplog.text + + @pytest.mark.parametrize("path", ["", "mock_path"]) async def test_api_proxy_get_request( api_client: TestClient,