aio-libs/aiohttp · error · RuntimeError

Cannot call write() after write_eof()

Error message

Cannot call write() after write_eof()

What it means

Once write_eof() has been called the response is finalized and the writer is torn down (self._payload_writer set to None). Calling write() afterwards is illegal because the body is already terminated; write() checks self._eof_sent at line 456 first.

Source

Thrown at aiohttp/web_response.py:457

        assert writer is not None
        # status line
        version = request.version
        status_line = f"HTTP/{version[0]}.{version[1]} {self._status} {self._reason}"
        await writer.write_headers(status_line, self._headers)

        # Send headers immediately if not opted into buffering
        if self._send_headers_immediately:
            writer.send_headers()

    async def write(
        self, data: Union[bytes, bytearray, "memoryview[int]", "memoryview[bytes]"]
    ) -> None:
        assert isinstance(
            data, (bytes, bytearray, memoryview)
        ), "data argument must be byte-ish (%r)" % type(data)

        if self._eof_sent:
            raise RuntimeError("Cannot call write() after write_eof()")
        if self._payload_writer is None:
            raise RuntimeError("Cannot call write() before prepare()")

        await self._payload_writer.write(data)

    async def drain(self) -> None:
        assert not self._eof_sent, "EOF has already been sent"
        assert self._payload_writer is not None, "Response has not been started"
        warnings.warn(
            "drain method is deprecated, use await resp.write()",
            DeprecationWarning,
            stacklevel=2,
        )
        await self._payload_writer.drain()

    async def write_eof(self, data: bytes = b"") -> None:
        assert isinstance(
            data, (bytes, bytearray, memoryview)

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Guard writes with if not resp.prepared or resp._eof_sent — better, track your own 'done' flag.
  2. Move streaming into the handler coroutine and return only after all writes complete.
  3. Cancel/clean up background tasks before the handler returns.

Example fix

# before
await resp.write_eof()
await resp.write(b'more')  # raises RuntimeError

# after
await resp.write(b'more')
await resp.write_eof()  # finalize once, last
Defensive patterns

Strategy: validation

Validate before calling

async def safe_write(resp, data):
    if resp._eof_sent:
        return  # response already finalized
    await resp.write(data)

Try / catch

try:
    await resp.write(data)
except RuntimeError as e:
    if 'after write_eof' in str(e):
        return  # ignore late writes from background tasks

Prevention

When it happens

Trigger: Calling await resp.write(data) after await resp.write_eof(), or after the framework has auto-finalized the response (e.g. returning from the handler after a manual write_eof, or a background task writing to an already-closed response).

Common situations: A cleanup/background coroutine writing to a response whose handler already returned; double-finalization; fire-and-forget tasks that outlive the request.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/721d7622bbd1a9e6.json. Report an issue: GitHub.