Python "RuntimeError: Event loop is closed" in CI
Async code touched an event loop that had already been closed. Usually a coroutine, transport, or cleanup callback ran after asyncio.run() (or a per-test loop) finished and tore the loop down.
What this error means
A program or test ends with RuntimeError: Event loop is closed, often during interpreter shutdown or test teardown (frequently from aiohttp/asyncpg transport finalizers on Windows or after asyncio.run). The main work may have succeeded; the error fires on cleanup.
Exception ignored in: <function _ProactorBasePipeTransport.__del__>
Traceback (most recent call last):
...
RuntimeError: Event loop is closedCommon causes
Loop reused after asyncio.run() closed it
asyncio.run() creates and then closes a loop. Scheduling work or closing a client outside that call touches an already-closed loop.
Per-test loop closed before async resources finalize
A test framework creates a fresh loop per test and closes it at teardown; async clients not awaited-closed inside the test then finalize against the dead loop.
How to fix it
Do all async work and cleanup inside one run
Open and close async resources within the same asyncio.run/async with scope so nothing outlives the loop.
async def main():
async with aiohttp.ClientSession() as session:
await do_work(session) # session closed before the loop ends
asyncio.run(main())Configure pytest-asyncio loop scope correctly
Match the async fixtures and tests to a consistent loop scope so resources are closed before the loop is.
[tool.pytest.ini_options]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"How to prevent it
- Scope async resource creation and teardown inside a single loop run.
- Use
async withfor clients so they close before the loop ends. - Set an explicit pytest-asyncio loop scope for async fixtures.