Problem
Two robustness gaps in the WebSocket layer:
1. Sequential broadcast with no timeout — packages/taskdog-server/src/taskdog_server/websocket/connection_manager.py:66-68:
for client_id, connection in list(self.active_connections.items()):
try:
await connection.send_json(message)
One slow or wedged peer (TCP window full, suspended terminal, laptop asleep) blocks the entire broadcast loop indefinitely; all other clients stop receiving events. With ~5 concurrent agent clients this is easy to hit.
2. Malformed frames kill the connection without close or log — packages/taskdog-server/src/taskdog_server/api/routers/websocket.py:80-91: receive_json raises on any non-JSON text frame; the bare except Exception removes the client from the manager but never calls websocket.close() and logs nothing. The peer sees a silently dead socket with no close frame to trigger its reconnect logic.
Direction
- Fan out with
asyncio.gather(..., return_exceptions=True), wrap each send in asyncio.wait_for(...), disconnect on timeout.
- Use
receive_text() + guarded json.loads; ignore malformed frames; await websocket.close(...) + logger.exception in the catch-all.
Found during repo-wide audit, 2026-07-25.
Problem
Two robustness gaps in the WebSocket layer:
1. Sequential broadcast with no timeout —
packages/taskdog-server/src/taskdog_server/websocket/connection_manager.py:66-68:One slow or wedged peer (TCP window full, suspended terminal, laptop asleep) blocks the entire broadcast loop indefinitely; all other clients stop receiving events. With ~5 concurrent agent clients this is easy to hit.
2. Malformed frames kill the connection without close or log —
packages/taskdog-server/src/taskdog_server/api/routers/websocket.py:80-91:receive_jsonraises on any non-JSON text frame; the bareexcept Exceptionremoves the client from the manager but never callswebsocket.close()and logs nothing. The peer sees a silently dead socket with no close frame to trigger its reconnect logic.Direction
asyncio.gather(..., return_exceptions=True), wrap each send inasyncio.wait_for(...), disconnect on timeout.receive_text()+ guardedjson.loads; ignore malformed frames;await websocket.close(...)+logger.exceptionin the catch-all.Found during repo-wide audit, 2026-07-25.