aio-libs/aiohttp · error · ClientConnectionError
Connection closed
Error message
Connection closed
What it means
Raised as ClientConnectionError('Connection closed') from ClientResponse.read() when the response has already been explicitly released (_released=True) and you then call read(). After release(), the underlying connection is returned to the pool and the body is no longer accessible, so read() refuses to hand back stale/missing data. This is distinct from a network drop during the initial read.
Solutions
- Read the body before releasing: call await resp.read() (or .text()/.json()) while still inside the response context.
- Cache the bytes you need: body = await resp.read(); then use body later instead of resp.read() again.
- Avoid calling resp.release() manually if you intend to read later; let the context manager handle lifecycle.
- For streaming, consume resp.content fully before exiting the context.
Example fix
# before
async with session.get(url) as resp:
pass
body = await resp.read() # ClientConnectionError: Connection closed
# after
async with session.get(url) as resp:
body = await resp.read() # read while alive
# use `body` afterwards Defensive patterns
Strategy: validation
Validate before calling
async def read_once(resp):
if resp.closed or getattr(resp, '_released', False):
raise RuntimeError('response already released; cannot read')
return await resp.read()
# cache the body and reuse it instead of re-reading
async with session.get(url) as resp:
body = await resp.read()
# use `body` later, not resp.read() Type guard
def response_is_readable(resp) -> bool:
return not getattr(resp, '_released', False) and not resp.closed Try / catch
from aiohttp import ClientConnectionError
try:
body = await resp.read()
except ClientConnectionError as e:
if str(e) == 'Connection closed':
# response was released; you must re-issue the request
resp = await session.get(url)
body = await resp.read()
else:
raise Prevention
- Read the body inside the 'async with' block; store bytes, not the response object.
- Never call release() before read().
- Treat the response as unusable once the context manager exits.
When it happens
Trigger: Calling resp.release() (or the context manager exiting and releasing), then calling resp.read() again on the same ClientResponse object. The _released flag is True and _body was never populated, so read() raises ClientConnectionError.
Common situations: Calling read() after the 'async with session.get() as resp' block has exited; calling release() manually then trying to read; storing the response object and using it after its lifecycle ended; mixing streaming (content.read()) with a later full read().
Understand the failure class
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
Related errors
- Cannot compute fallback encoding of a not yet read body
- Attempt to decode JSON with unexpected mimetype
- Connection closed.
- Connection timeout to host
- data argument must be str (%r)
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/3f6cda4a5e28a431.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/client_reqrep.py:706
async def _on_chunk_response_received(self, chunk: bytes) -> None:
try:
for trace in self._traces:
await trace.send_response_chunk_received(self.method, self.url, chunk)
except BaseException:
self.close()
raise
async def read(self) -> bytes:
"""Read response payload."""
if self._body is None:
try:
self._body = await self.content.read()
except BaseException:
self.close()
raise
elif self._released: # Response explicitly released
raise ClientConnectionError("Connection closed")
protocol = self._connection and self._connection.protocol
if protocol is None or not protocol.upgraded:
await self._wait_released() # Underlying connection released
return self._body
def get_encoding(self) -> str:
ctype = self.headers.get(hdrs.CONTENT_TYPE, "").lower()
mimetype = parse_mimetype(ctype)
encoding = mimetype.parameters.get("charset")
if encoding:
with contextlib.suppress(LookupError, ValueError):
return codecs.lookup(encoding).name
if mimetype.type == "application" and (
mimetype.subtype == "json" or mimetype.subtype == "rdap"
):View on GitHub (pinned to d041d4d0fd)