aio-libs/aiohttp · error · RuntimeError

Cannot call write() before prepare()

Error message

Cannot call write() before prepare()

What it means

Raised by Response.write() when _payload_writer is None, i.e. prepare() was never called for this response. The writer is the channel that actually pushes bytes onto the socket; without it write() has nowhere to send data. The error prevents silently dropping the body.

Solutions

  1. Always call `await resp.prepare(request)` before any `resp.write()`.
  2. Use the higher-level `Response(body=...)` for fixed-size bodies — it handles prepare/write_eof automatically.
  3. In error/cleanup paths, track whether prepare() succeeded before attempting writes.
  4. If prepare() may have been skipped on an exception, return a fresh Response instead of writing to the unprepared one.

Example fix

// before
async def handler(request):
    resp = StreamResponse()
    await resp.write(b'hi')  # RuntimeError: no prepare()
    return resp

// after
async def handler(request):
    resp = StreamResponse()
    await resp.prepare(request)
    await resp.write(b'hi')
    await resp.write_eof()
    return resp
Defensive patterns

Strategy: validation

Validate before calling

async def write_safe(resp, data):
    if getattr(resp, '_payload_writer', None) is None:
        await resp.prepare(resp._req)
    await resp.write(data)

Type guard

def is_prepared(resp) -> bool:
    return getattr(resp, '_payload_writer', None) is not None

Prevention

When it happens

Trigger: Calling `await resp.write(data)` on a StreamResponse without first `await resp.prepare(request)`. Also triggered when prepare() raises or is interrupted and code continues to write() in a finally/except block.

Common situations: New aiohttp users forgetting the prepare() step; refactoring that moves prepare() into a conditional branch that is skipped on an error path; middleware that writes to a response it did not prepare.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/4a37cdc80d251447. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/web_response.py:459

        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)
        ), "data argument must be byte-ish (%r)" % type(data)

View on GitHub (pinned to d041d4d0fd)