python/cpython · error · RuntimeError
TaskGroup {self!r} is finished
Error message
TaskGroup {self!r} is finished What it means
RuntimeError raised by TaskGroup.create_task() when the group is exiting with no remaining tasks (_exiting and not self._tasks): the body has finished and __aexit__ is winding down, so new work cannot join. The passed coroutine is closed before raising.
Source
Thrown at Lib/asyncio/taskgroups.py:223
) from None
finally:
exc = None
# Suppress any remaining exception (exceptions deserving to be raised
# were raised above).
return True
def create_task(self, coro, **kwargs):
"""Create a new task in this group and return it.
Similar to `asyncio.create_task`.
"""
if not self._entered:
coro.close()
raise RuntimeError(f"TaskGroup {self!r} has not been entered")
if self._exiting and not self._tasks:
coro.close()
raise RuntimeError(f"TaskGroup {self!r} is finished")
if self._aborting:
coro.close()
raise RuntimeError(f"TaskGroup {self!r} is shutting down")
task = self._loop.create_task(coro, **kwargs)
futures.future_add_to_awaited_by(task, self._parent_task)
# Always schedule the done callback even if the task is
# already done (e.g. if the coro was able to complete eagerly),
# otherwise if the task completes with an exception then it will cancel
# the current task too early. gh-128550, gh-128588
self._tasks.add(task)
task.add_done_callback(self._on_task_done)
try:
return task
finally:
# gh-128552: prevent a refcycle of
# task.exception().__traceback__->TaskGroup.create_task->taskView on GitHub (pinned to bc6749cc3b)
Solutions
- Keep spawn logic inside the live body of the 'async with' block and await completion there
- For fire-and-forget work that must outlive the group, use asyncio.create_task on the loop (and track it separately) rather than the dying group
- Restructure continuation patterns: the task that finishes should itself schedule its successor before returning, while the group is still active
Example fix
// before
async with asyncio.TaskGroup() as tg:
t = tg.create_task(job())
t.add_done_callback(lambda _: tg.create_task(cleanup())) # may fire during exit -> RuntimeError
// after
async def job_then_cleanup():
await job()
await cleanup()
async with asyncio.TaskGroup() as tg:
tg.create_task(job_then_cleanup()) Defensive patterns
Strategy: validation
Validate before calling
null
Type guard
null
Try / catch
null
Prevention
- Spawn continuations from within the live body, never from done-callbacks into the same group
- Work that must outlive the group belongs to asyncio.create_task on the loop or a new group
- Chain successor work inside the task itself before it returns
When it happens
Trigger: A task-finished callback or scheduled call that invokes tg.create_task() while the 'async with' body is completing; e.g. calling create_task from on_done callbacks, from a sibling task's finally block racing the exit, or from code woken by the last task completing.
Common situations: Chained/continuation spawning inside the group ('when X finishes, spawn Y') without awaiting Y within the body; fire-and-forget helpers that lazily create tasks and get invoked at teardown; races between a worker finishing and a supervisor trying to enqueue a replacement at shutdown.
Related errors
- TaskGroup {self!r} has already been entered
- unhandled errors in a TaskGroup
- TaskGroup {self!r} has not been entered
- TaskGroup {self!r} is shutting down
- {htmldir!r} is not a Sphinx HTML output directory
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/429abe36d77e14b4.
Report an issue: GitHub.