{"record":{"id":"c11a1db9c2a60284","repo":"aio-libs/aiohttp","slug":"already-started","errorCode":null,"errorMessage":"Already started","messagePattern":"Already started","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_ws.py","lineNumber":402,"sourceCode":"        loop = self._loop\n        assert loop is not None\n        self._reader = WebSocketDataQueue(\n            request._protocol, DEFAULT_CHUNK_SIZE, loop=loop\n        )\n        parser = WebSocketReader(\n            self._reader,\n            self._max_msg_size,\n            compress=bool(self._compress),\n            decode_text=self._decode_text,\n        )\n        cb = None if self._heartbeat is None else self._on_data_received\n        request.protocol.set_parser(parser, data_received_cb=cb)\n        # disable HTTP keepalive for WebSocket\n        request.protocol.keep_alive(False)\n\n    def can_prepare(self, request: BaseRequest) -> WebSocketReady:\n        if self._writer is not None:\n            raise RuntimeError(\"Already started\")\n        try:\n            _, protocol, _, _ = self._handshake(request)\n        except HTTPException:\n            return WebSocketReady(False, None)\n        else:\n            return WebSocketReady(True, protocol)\n\n    @property\n    def prepared(self) -> bool:\n        return self._writer is not None\n\n    @property\n    def closed(self) -> bool:\n        return self._closed\n\n    @property\n    def close_code(self) -> int | None:\n        return self._close_code","sourceCodeStart":384,"sourceCodeEnd":420,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_ws.py#L384-L420","documentation":"WebSocketResponse.can_prepare() raises RuntimeError('Already started') if self._writer is already set — meaning prepare()/_post_start() has already run for this WebSocketResponse instance. A single WebSocketResponse is a stateful object tied to one connection lifecycle; calling can_prepare() (or prepare()) twice is a programmer error. The check uses the same _writer sentinel as the prepared property.","triggerScenarios":"Calling await ws.can_prepare(request) after await ws.prepare(request); reusing the same WebSocketResponse object across two requests; a handler that calls can_prepare() then unconditionally calls prepare() again inside an if-branch that already prepared.","commonSituations":"Middleware and handler both calling prepare(); reusing a module-level WebSocketResponse; refactoring that moved prepare() but left a duplicate; buggy reconnection logic reusing the WS object.","solutions":["Create a fresh WebSocketResponse() per request: ws = web.WebSocketResponse() inside the handler.","Call can_prepare() and prepare() exactly once per request — guard with the prepared property if needed.","Don't share a WebSocketResponse across requests or store it on a long-lived object."],"exampleFix":"// before — reused module-level ws\n_WS = web.WebSocketResponse()\nasync def handler(request):\n    if _WS.can_prepare(request).ok:  # raises second time\n        await _WS.prepare(request)\n// after\nasync def handler(request):\n    ws = web.WebSocketResponse()\n    if ws.can_prepare(request).ok:\n        await ws.prepare(request)","handlingStrategy":"validation","validationCode":"async def handler(request):\n    ws = web.WebSocketResponse()  # fresh per request\n    if ws.prepared:  # always False here; sanity check\n        raise RuntimeError('reuse of WebSocketResponse')\n    ready = ws.can_prepare(request)\n    if not ready.ok:\n        return web.Response(status=400)\n    await ws.prepare(request)\n    # ...","typeGuard":"def ws_is_fresh(ws: web.WebSocketResponse) -> bool:\n    return not ws.prepared","tryCatchPattern":"ws = web.WebSocketResponse()\ntry:\n    ready = ws.can_prepare(request)\nexcept RuntimeError as e:\n    if 'Already started' in str(e):\n        raise RuntimeError('WebSocketResponse reused across requests — create a new one per handler call')\n    raise","preventionTips":["Create a new WebSocketResponse() inside each handler — never reuse.","Call can_prepare()/prepare() at most once per request.","Don't store WebSocketResponse on the Application or module level."],"tags":["websocket","lifecycle","state","programmer-error"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}