aio-libs/aiohttp · error · RuntimeError
Response has not been started
Error message
Response has not been started
What it means
Raised by WebSocketResponse.write_eof() when self._payload_writer is None. _payload_writer is set by StreamResponse.prepare() (the parent class), i.e. the HTTP response body must have been started. write_eof is normally invoked by the framework during connection teardown; reaching it with no payload writer means teardown ran on a response that never started.
Solutions
- Ensure `await ws.prepare(request)` completes before any code path can return the response.
- Do not call ws.write_eof() yourself — the framework closes the WebSocket via ws.close(); call close() instead.
- If you catch exceptions around prepare(), re-raise or return a plain HTTP response instead of the unprepared ws.
- Inspect ws.prepared before any manual EOF/close path.
Example fix
// before
async def handler(request):
ws = web.WebSocketResponse()
try:
await ws.prepare(request)
except Exception:
pass
return ws # framework calls write_eof -> raises
// after
async def handler(request):
ws = web.WebSocketResponse()
await ws.prepare(request)
...
await ws.close()
return ws Defensive patterns
Strategy: validation
Validate before calling
if ws.prepared:
await ws.write_eof()
# better: do not call write_eof yourself; rely on the framework / ws.close() Type guard
def ws_has_payload(ws: web.WebSocketResponse) -> bool:
return getattr(ws, '_payload_writer', None) is not None Try / catch
try:
await ws.write_eof()
except RuntimeError as e:
if 'not been started' in str(e):
pass # nothing to end
else:
raise Prevention
- Never call ws.write_eof() yourself — call ws.close() and let the framework finalize.
- Do not swallow exceptions from prepare(); if it fails, return a plain Response.
- Assert ws.prepared before any teardown that could call write_eof.
When it happens
Trigger: The server / runner calls write_eof() on a WebSocketResponse whose prepare() raised or was never awaited; an explicit `await ws.write_eof()` in user code before prepare(); connection cleanup racing after a handshake failure.
Common situations: Catching and swallowing an exception from prepare() then letting the handler return the ws (framework then tries to finalize/write_eof); a custom runner or middleware that writes EOF unconditionally.
Related errors
- WebSocket connection is closed.
- 1002
- Already started
- Call .prepare() first
- Cannot call write() after write_eof()
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/485b58954e65fe4a.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/web_ws.py:505
async def send_json_bytes(
self,
data: Any,
compress: int | None = None,
*,
dumps: JSONBytesEncoder,
) -> None:
"""Send JSON data using a bytes-returning encoder as a binary frame.
Use this when your JSON encoder (like orjson) returns bytes
instead of str, avoiding the encode/decode overhead.
"""
await self.send_bytes(dumps(data), compress=compress)
async def write_eof(self) -> None: # type: ignore[override]
if self._eof_sent:
return
if self._payload_writer is None:
raise RuntimeError("Response has not been started")
await self.close()
self._eof_sent = True
async def close(
self, *, code: int = WSCloseCode.OK, message: bytes = b"", drain: bool = True
) -> bool:
"""Close websocket connection."""
if self._writer is None:
raise RuntimeError("Call .prepare() first")
if self._closed:
return False
self._set_closed()
try:
await self._writer.close(code, message)
writer = self._payload_writerView on GitHub (pinned to d041d4d0fd)