{"id":"485b58954e65fe4a","repo":"aio-libs/aiohttp","slug":"response-has-not-been-started","errorCode":null,"errorMessage":"Response has not been started","messagePattern":"Response has not been started","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_ws.py","lineNumber":505,"sourceCode":"    async def send_json_bytes(\n        self,\n        data: Any,\n        compress: int | None = None,\n        *,\n        dumps: JSONBytesEncoder,\n    ) -> None:\n        \"\"\"Send JSON data using a bytes-returning encoder as a binary frame.\n\n        Use this when your JSON encoder (like orjson) returns bytes\n        instead of str, avoiding the encode/decode overhead.\n        \"\"\"\n        await self.send_bytes(dumps(data), compress=compress)\n\n    async def write_eof(self) -> None:  # type: ignore[override]\n        if self._eof_sent:\n            return\n        if self._payload_writer is None:\n            raise RuntimeError(\"Response has not been started\")\n\n        await self.close()\n        self._eof_sent = True\n\n    async def close(\n        self, *, code: int = WSCloseCode.OK, message: bytes = b\"\", drain: bool = True\n    ) -> bool:\n        \"\"\"Close websocket connection.\"\"\"\n        if self._writer is None:\n            raise RuntimeError(\"Call .prepare() first\")\n\n        if self._closed:\n            return False\n        self._set_closed()\n\n        try:\n            await self._writer.close(code, message)\n            writer = self._payload_writer","sourceCodeStart":487,"sourceCodeEnd":523,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/web_ws.py#L487-L523","documentation":"Raised by WebSocketResponse.write_eof() when self._payload_writer is None. _payload_writer is set by the parent StreamResponse.prepare(); for a WebSocketResponse the real setup happens in its overridden prepare(). Calling write_eof before the response has been started (prepared) means there is no writer to flush EOF to.","triggerScenarios":"Invoking `await ws.write_eof()` on a WebSocketResponse that never had `await ws.prepare(request)` called; or framework/middleware code that calls write_eof() on a response object whose prepare() was skipped or failed before assigning the writer.","commonSituations":"Middleware or cleanup hooks that unconditionally call write_eof() on any response; a handler that returns early before prepare(); an exception path that aborts after constructing the WebSocketResponse but before prepare() completes.","solutions":["Ensure `await ws.prepare(request)` completes successfully before any code path that may call write_eof().","In generic middleware, check `ws.prepared` (or `ws._payload_writer is not None`) before calling write_eof().","Let the normal close() path handle end-of-stream instead of calling write_eof() directly."],"exampleFix":"# before\nws = web.WebSocketResponse()\nawait ws.write_eof()\n\n# after\nws = web.WebSocketResponse()\nawait ws.prepare(request)\nawait ws.write_eof()  # delegates to close()","handlingStrategy":"validation","validationCode":"async def safe_write_eof(ws: web.WebSocketResponse) -> None:\n    if ws._payload_writer is None:\n        return  # nothing started, nothing to flush\n    await ws.write_eof()","typeGuard":"def response_started(ws) -> bool:\n    return getattr(ws, \"_payload_writer\", None) is not None","tryCatchPattern":"try:\n    await ws.write_eof()\nexcept RuntimeError as e:\n    if \"Response has not been started\" not in str(e):\n        raise","preventionTips":["In middleware, check `ws.prepared` before calling write_eof().","Prefer close() over a direct write_eof() for WebSockets.","Avoid early returns that skip prepare() on the WebSocketResponse."],"tags":["websocket","server","lifecycle","eof"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}