aio-libs/aiohttp · error · RuntimeError
Session is closed
Error message
Session is closed
What it means
Raised at the top of ClientSession.request (and therefore every .get/.post/...) when self.closed is True. Once close() has run (or the async with block has exited) the session's connector is gone and no further requests can be made.
Solutions
- Create a new session for the new work: async with ClientSession() as s: ....
- Structure ownership so the session's lifetime encloses every task that uses it.
- Track closed state yourself and skip / reopen before calling request methods.
Example fix
// before
session = ClientSession()
await session.close()
await session.get('http://x') # raises
// after
async with ClientSession() as session:
await session.get('http://x')
# open a fresh session for further work Defensive patterns
Strategy: try-catch
Validate before calling
async def safe_get(session, url):
if session.closed:
raise RuntimeError('session closed; open a new one')
return await session.get(url) Type guard
def is_open(session) -> bool:
return not session.closed Try / catch
try:
resp = await session.get(url)
except RuntimeError as e:
if 'Session is closed' in str(e):
async with ClientSession() as new_session:
resp = await new_session.get(url)
else:
raise Prevention
- Scope session lifetime with 'async with ClientSession() as s:'.
- Track session ownership in one place and never use after close().
- Add a liveness check helper for long-lived background tasks.
When it happens
Trigger: Calling session.get(...) after await session.close(). Calling a request method on a session that was used as async with session: ... and the block has exited. Calling from a task that outlived the session's owner task.
Common situations: Reusing a module-level session after shutdown. Holding a session reference in a long-lived object whose cleanup ran. Test fixtures closing the session in teardown while an assertion still issues a request.
Related errors
- Already started
- base_url must have a trailing '/'
- Call .prepare() first
- Call runner.setup() before making a site
- Cannot add frozen application
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/37192122b7632643.
Report an issue: GitHub.
Appendix: 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 d041d4d0fd)