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
- Guard writes with if not resp.prepared or resp._eof_sent — better, track your own 'done' flag.
- Move streaming into the handler coroutine and return only after all writes complete.
- 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
- Centralize all writes in the handler coroutine and return only when done.
- Track your own 'finalized' flag instead of relying on internals.
- Cancel background tasks before the handler returns.
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
- Cannot call write() before prepare()
- Got more than {limit} bytes when reading: {line!r}.
- Can not decode content-encoding: brotli (br). Please install
- Can not decode content-encoding: zstandard (zstd). Please in
- Can not decode content-encoding: %s
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/721d7622bbd1a9e6.json.
Report an issue: GitHub.