{"record":{"id":"d0ac66c536d77a8e","repo":"aio-libs/aiohttp","slug":"call-prepare-first","errorCode":null,"errorMessage":"Call .prepare() first","messagePattern":"Call \\.prepare\\(\\) first","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_ws.py","lineNumber":445,"sourceCode":"    def compress(self) -> int | bool:\n        return self._compress\n\n    def get_extra_info(self, name: str, default: Any = None) -> Any:\n        \"\"\"Get optional transport information.\n\n        If no value associated with ``name`` is found, ``default`` is returned.\n        \"\"\"\n        writer = self._writer\n        if writer is None:\n            return default\n        return writer.transport.get_extra_info(name, default)\n\n    def exception(self) -> BaseException | None:\n        return self._exception\n\n    async def ping(self, message: bytes = b\"\") -> None:\n        if self._writer is None:\n            raise RuntimeError(\"Call .prepare() first\")\n        await self._writer.send_frame(message, WSMsgType.PING)\n\n    async def pong(self, message: bytes = b\"\") -> None:\n        # unsolicited pong\n        if self._writer is None:\n            raise RuntimeError(\"Call .prepare() first\")\n        await self._writer.send_frame(message, WSMsgType.PONG)\n\n    async def send_frame(\n        self, message: bytes, opcode: WSMsgType, compress: int | None = None\n    ) -> None:\n        \"\"\"Send a frame over the websocket.\"\"\"\n        if self._writer is None:\n            raise RuntimeError(\"Call .prepare() first\")\n        await self._writer.send_frame(message, opcode, compress)\n\n    async def send_str(self, data: str, compress: int | None = None) -> None:\n        if self._writer is None:","sourceCodeStart":427,"sourceCodeEnd":463,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_ws.py#L427-L463","documentation":"WebSocketResponse.ping() raises RuntimeError if self._writer is None — i.e. prepare() has not been called yet (or failed). ping() needs the WebSocketWriter to send a frame over the live connection. This is the same _writer sentinel used throughout. Until prepare() succeeds, there is no transport to send on.","triggerScenarios":"Calling await ws.ping() before await ws.prepare(request); calling ping() in a code path where prepare() raised or was never reached; background tasks started before the handshake completed.","commonSituations":"Sending pings from a heartbeat task spawned before prepare(); refactoring that moved prepare() below the first ping; conditional prepare() where a branch skips it but still calls ping().","solutions":["Always call and await ws.prepare(request) before any ping/pong/send_*.","Guard with: if not ws.prepared: return, or check ws.prepared before pinging.","Spawn heartbeat tasks only after prepare() has returned successfully."],"exampleFix":"// before\nasync def handler(request):\n    ws = web.WebSocketResponse()\n    await ws.ping()  # raises\n    await ws.prepare(request)\n// after\nasync def handler(request):\n    ws = web.WebSocketResponse()\n    await ws.prepare(request)\n    await ws.ping()","handlingStrategy":"validation","validationCode":"async def safe_ping(ws):\n    if not ws.prepared:\n        return\n    await ws.ping()","typeGuard":"def ws_ready_for_control(ws: web.WebSocketResponse) -> bool:\n    return ws.prepared and not ws.closed","tryCatchPattern":"try:\n    await ws.ping()\nexcept RuntimeError as e:\n    if 'prepare' in str(e).lower():\n        log.debug('ping before prepare; skipping')\n    else:\n        raise","preventionTips":["Always await ws.prepare(request) before ping().","Spawn heartbeat tasks only after prepare() returns.","Check ws.prepared before sending control frames."],"tags":["websocket","lifecycle","ping","prepare"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}