aio-libs/aiohttp · error · RuntimeError
Cannot call write() after write_eof()
Error message
Cannot call write() after write_eof()
What it means
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.
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.
Example fix
// before
async def handler(request):
resp = StreamResponse()
await resp.prepare(request)
asyncio.create_task(pump(resp)) # task outlives handler
return resp # framework calls write_eof; pump() then write() -> RuntimeError
// after
async def handler(request):
resp = StreamResponse()
await resp.prepare(request)
await pump(resp) # fully drain before returning
await resp.write_eof()
return resp Defensive patterns
Strategy: validation
Validate before calling
async def safe_write(resp, data):
if getattr(resp, '_eof_sent', False):
return False
await resp.write(data)
return True Type guard
def can_write(resp) -> bool:
return not getattr(resp, '_eof_sent', False) Try / catch
try:
await resp.write(data)
except RuntimeError:
# response already finalized; log and stop the producer
pass Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Cannot call write() before prepare()
- Using chunked encoding is forbidden for HTTP/
- body and text are not allowed together
- Call runner.setup() before making a site
- charset must not be in content_type argument
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/721d7622bbd1a9e6.
Report an issue: GitHub.
Appendix: 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 d041d4d0fd)