aio-libs/aiohttp · error · RuntimeError
Call .prepare() first
Error message
Call .prepare() first
What it means
WebSocketResponse.ping() raises RuntimeError if self._writer is None — i.e. prepare() has not been called yet (or failed). ping() needs the WebSocketWriter to send a frame over the live connection. This is the same _writer sentinel used throughout. Until prepare() succeeds, there is no transport to send on.
Solutions
- Always call and await ws.prepare(request) before any ping/pong/send_*.
- Guard with: if not ws.prepared: return, or check ws.prepared before pinging.
- Spawn heartbeat tasks only after prepare() has returned successfully.
Example fix
// before
async def handler(request):
ws = web.WebSocketResponse()
await ws.ping() # raises
await ws.prepare(request)
// after
async def handler(request):
ws = web.WebSocketResponse()
await ws.prepare(request)
await ws.ping() Defensive patterns
Strategy: validation
Validate before calling
async def safe_ping(ws):
if not ws.prepared:
return
await ws.ping() Type guard
def ws_ready_for_control(ws: web.WebSocketResponse) -> bool:
return ws.prepared and not ws.closed Try / catch
try:
await ws.ping()
except RuntimeError as e:
if 'prepare' in str(e).lower():
log.debug('ping before prepare; skipping')
else:
raise Prevention
- Always await ws.prepare(request) before ping().
- Spawn heartbeat tasks only after prepare() returns.
- Check ws.prepared before sending control frames.
When it happens
Trigger: Calling await ws.ping() before await ws.prepare(request); calling ping() in a code path where prepare() raised or was never reached; background tasks started before the handshake completed.
Common situations: Sending pings from a heartbeat task spawned before prepare(); refactoring that moved prepare() below the first ping; conditional prepare() where a branch skips it but still calls ping().
Related errors
- Already started
- Cannot call write() before prepare()
- Connection lost
- Response has not been started
- WebSocket connection is closed.
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/d0ac66c536d77a8e.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/web_ws.py:445
def compress(self) -> int | bool:
return self._compress
def get_extra_info(self, name: str, default: Any = None) -> Any:
"""Get optional transport information.
If no value associated with ``name`` is found, ``default`` is returned.
"""
writer = self._writer
if writer is None:
return default
return writer.transport.get_extra_info(name, default)
def exception(self) -> BaseException | None:
return self._exception
async def ping(self, message: bytes = b"") -> None:
if self._writer is None:
raise RuntimeError("Call .prepare() first")
await self._writer.send_frame(message, WSMsgType.PING)
async def pong(self, message: bytes = b"") -> None:
# unsolicited pong
if self._writer is None:
raise RuntimeError("Call .prepare() first")
await self._writer.send_frame(message, WSMsgType.PONG)
async def send_frame(
self, message: bytes, opcode: WSMsgType, compress: int | None = None
) -> None:
"""Send a frame over the websocket."""
if self._writer is None:
raise RuntimeError("Call .prepare() first")
await self._writer.send_frame(message, opcode, compress)
async def send_str(self, data: str, compress: int | None = None) -> None:
if self._writer is None:View on GitHub (pinned to d041d4d0fd)