{"record":{"id":"721d7622bbd1a9e6","repo":"aio-libs/aiohttp","slug":"cannot-call-write-after-write-eof","errorCode":null,"errorMessage":"Cannot call write() after write_eof()","messagePattern":"Cannot call write\\(\\) after write_eof\\(\\)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"aiohttp/web_response.py","lineNumber":457,"sourceCode":"        assert writer is not None\n        # status line\n        version = request.version\n        status_line = f\"HTTP/{version[0]}.{version[1]} {self._status} {self._reason}\"\n        await writer.write_headers(status_line, self._headers)\n\n        # Send headers immediately if not opted into buffering\n        if self._send_headers_immediately:\n            writer.send_headers()\n\n    async def write(\n        self, data: Union[bytes, bytearray, \"memoryview[int]\", \"memoryview[bytes]\"]\n    ) -> None:\n        assert isinstance(\n            data, (bytes, bytearray, memoryview)\n        ), \"data argument must be byte-ish (%r)\" % type(data)\n\n        if self._eof_sent:\n            raise RuntimeError(\"Cannot call write() after write_eof()\")\n        if self._payload_writer is None:\n            raise RuntimeError(\"Cannot call write() before prepare()\")\n\n        await self._payload_writer.write(data)\n\n    async def drain(self) -> None:\n        assert not self._eof_sent, \"EOF has already been sent\"\n        assert self._payload_writer is not None, \"Response has not been started\"\n        warnings.warn(\n            \"drain method is deprecated, use await resp.write()\",\n            DeprecationWarning,\n            stacklevel=2,\n        )\n        await self._payload_writer.drain()\n\n    async def write_eof(self, data: bytes = b\"\") -> None:\n        assert isinstance(\n            data, (bytes, bytearray, memoryview)","sourceCodeStart":439,"sourceCodeEnd":475,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/web_response.py#L439-L475","documentation":"Raised by Response.write() after write_eof() has already been called on the response, meaning the body stream was already terminated. Writing after EOF would either be silently dropped or, worse, inject bytes into a subsequent response, so aiohttp treats it as a hard error.","triggerScenarios":"Calling `await resp.write(data)` after `await resp.write_eof()` (often indirectly via the response being returned from the handler, which triggers automatic EOF). Also triggered by a background task or callback that writes to a response whose handler already returned.","commonSituations":"Handler returns the response, framework calls write_eof, then an `asyncio.create_task` started inside the handler tries to stream more data. Mixing manual write_eof with returning the response. SSE/WebSocket cleanup paths that write a final frame after EOF.","solutions":["Do not write after returning the response from the handler — keep all writes inside the handler coroutine.","Check `if not resp._eof_sent:` before writing in fire-and-forget tasks, or signal the task to stop before the handler returns.","Avoid calling write_eof() manually; let the framework finalize the response.","Restructure SSE/streaming code so the producer task is awaited (or properly cancelled) inside the handler scope."],"exampleFix":"// before\nasync def handler(request):\n    resp = StreamResponse()\n    await resp.prepare(request)\n    asyncio.create_task(pump(resp))  # task outlives handler\n    return resp  # framework calls write_eof; pump() then write() -> RuntimeError\n\n// after\nasync def handler(request):\n    resp = StreamResponse()\n    await resp.prepare(request)\n    await pump(resp)        # fully drain before returning\n    await resp.write_eof()\n    return resp","handlingStrategy":"validation","validationCode":"async def safe_write(resp, data):\n    if getattr(resp, '_eof_sent', False):\n        return False\n    await resp.write(data)\n    return True","typeGuard":"def can_write(resp) -> bool:\n    return not getattr(resp, '_eof_sent', False)","tryCatchPattern":"try:\n    await resp.write(data)\nexcept RuntimeError:\n    # response already finalized; log and stop the producer\n    pass","preventionTips":["Keep all response writes inside the handler coroutine.","Await or cancel background producer tasks before returning the response.","Do not call write_eof() manually; let the framework finalize.","Check _eof_sent before writing from fire-and-forget tasks."],"tags":["aiohttp","web-response","streaming","eof","lifecycle"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}