Skip to content

Commit 1cd499b

Browse files
agnersclaude
andcommitted
Fix WebSocket transport None race condition in proxy
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 <noreply@anthropic.com>
1 parent 53a8044 commit 1cd499b

File tree

2 files changed

+33
-0
lines changed

2 files changed

+33
-0
lines changed

supervisor/api/proxy.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,11 @@ async def websocket(self, request: web.Request):
222222
raise HTTPBadGateway()
223223
_LOGGER.info("Home Assistant WebSocket API request initialize")
224224

225+
# Check if transport is still valid before WebSocket upgrade
226+
if request.transport is None:
227+
_LOGGER.warning("WebSocket connection lost before upgrade")
228+
raise web.HTTPBadRequest(reason="Connection closed")
229+
225230
# init server
226231
server = web.WebSocketResponse(heartbeat=30)
227232
await server.prepare(request)

tests/api/test_proxy.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,34 @@ async def test_proxy_auth_abort_log(
223223
)
224224

225225

226+
async def test_websocket_transport_none(
227+
coresys,
228+
caplog: pytest.LogCaptureFixture,
229+
):
230+
"""Test WebSocket connection with transport None is handled gracefully."""
231+
from aiohttp import web
232+
233+
# Get the API proxy instance from coresys
234+
api_proxy = APIProxy.__new__(APIProxy)
235+
api_proxy.coresys = coresys
236+
237+
# Create a mock request with transport set to None to simulate connection loss
238+
mock_request = AsyncMock(spec=web.Request)
239+
mock_request.transport = None
240+
241+
caplog.clear()
242+
with caplog.at_level(logging.WARNING):
243+
# This should raise HTTPBadRequest, not AssertionError
244+
with pytest.raises(web.HTTPBadRequest) as exc_info:
245+
await api_proxy.websocket(mock_request)
246+
247+
# Verify the error reason
248+
assert exc_info.value.reason == "Connection closed"
249+
250+
# Verify the warning was logged
251+
assert "WebSocket connection lost before upgrade" in caplog.text
252+
253+
226254
@pytest.mark.parametrize("path", ["", "mock_path"])
227255
async def test_api_proxy_get_request(
228256
api_client: TestClient,

0 commit comments

Comments
 (0)