aio-libs/aiohttp · error · RuntimeError
Session is closed
Error message
Session is closed
What it means
Raised at the top of `ClientSession._request` (client.py:502-503) when `self.closed` is True. A session becomes closed after `.close()` completes or after exiting its `async with` block. Any subsequent call into `request/get/post/ws_connect/...` routes through `_request` and hits this guard before doing any work.
Source
Thrown at aiohttp/client.py:503
proxy: StrOrURL | None = None,
timeout: ClientTimeout | _SENTINEL | None = sentinel,
ssl: SSLContext | bool | Fingerprint | _SENTINEL = sentinel,
server_hostname: str | None = None,
proxy_headers: LooseHeaders | None = None,
trace_request_ctx: object = None,
read_bufsize: int | None = None,
auto_decompress: bool | None = None,
max_line_size: int | None = None,
max_field_size: int | None = None,
max_headers: int | None = None,
middlewares: Sequence[ClientMiddlewareType] | None = None,
) -> ClientResponse:
# NOTE: timeout clamps existing connect and read timeouts. We cannot
# set the default to None because we need to detect if the user wants
# to use the existing timeouts by setting timeout to None.
if self.closed:
raise RuntimeError("Session is closed")
method = method.upper()
if ssl is sentinel:
ssl = self._default_ssl
if not isinstance(ssl, SSL_ALLOWED_TYPES):
raise TypeError(
"ssl should be SSLContext, Fingerprint, or bool, "
f"got {ssl!r} instead."
)
if data is not None and json is not None:
raise ValueError(
"data and json parameters can not be used at the same time"
)
elif json is not None:
if self._json_serialize_bytes is not None:
data = payload.JsonBytesPayload(json, dumps=self._json_serialize_bytes)View on GitHub (pinned to c0ef574e29)
Solutions
- Ensure all requests finish before the session's `async with` block exits or before `await session.close()`.
- Restructure so the session owns the lifetime of the response (read/consume the body inside the context).
- Guard call sites: `if not session.closed: await session.get(...)`.
Example fix
// before
async with aiohttp.ClientSession() as s:
resp = await s.get(url)
return await resp.read() # block already exited -> Session is closed on next use
// after
async with aiohttp.ClientSession() as s:
resp = await s.get(url)
return await resp.read() # consume inside the context Defensive patterns
Strategy: try-catch
Validate before calling
def ensure_open(session):
if session.closed:
raise RuntimeError('ClientSession is closed; create a new one')
return session Type guard
def is_session_usable(session) -> bool:
return not session.closed Try / catch
try:
resp = await session.get(url)
except RuntimeError as e:
if str(e) == 'Session is closed':
session = await new_session() # rebuild
resp = await session.get(url)
else:
raise Prevention
- Scope response reading inside the session's `async with` block.
- Check `session.closed` before reusing a cached session.
- In background tasks, hold a strong reference to the session for the task's lifetime.
- Use `async with` consistently rather than manual close().
When it happens
Trigger: Using the session after `await session.close()`, after the `async with aiohttp.ClientSession() as s:` block exits, or after the connector was closed separately. Also from a `__del__`-triggered path or a fire-and-forget task that outlives the session scope.
Common situations: Returning a ClientResponse from an `async with` block and awaiting it later; background tasks holding a session reference after the app shutdown closed it; test fixtures closing the session in teardown while a coroutine is still pending.
Related errors
- Invalid window size
- Extension for deflate not supported{ext}
- Cannot write to closing transport
- Connection lost
- Connection timeout to host {url}
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/37192122b7632643.json.
Report an issue: GitHub.