python/cpython · error · RuntimeError

TaskGroup {self!r} is shutting down

Error message

TaskGroup {self!r} is shutting down

What it means

RuntimeError raised by TaskGroup.create_task() when the group is aborting (_aborting), i.e. a child task failed and the group is cancelling all siblings during teardown. Accepting new work during shutdown would leak a task no one awaits, so the coroutine is closed and the error raised.

Source

Thrown at Lib/asyncio/taskgroups.py:226

        # 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->task
            del task

    # Since Python 3.8 Tasks propagate all exceptions correctly,

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Handle failures inside task bodies (catch and decide) instead of reacting from outside with more create_task calls
  2. If work must continue after a failure, run it outside the failed group in a new TaskGroup or as an independent task
  3. Check group state assumptions: anything that can call create_task after the body's last await must be restructured to run within the body

Example fix

// before
async with asyncio.TaskGroup() as tg:
    tg.create_task(worker(queue))
# elsewhere, reacting to a worker error:
tg.create_task(worker(queue))  # group is aborting -> RuntimeError

// after
async def resilient_worker(queue):
    while True:
        item = await queue.get()
        try:
            await handle(item)
        except Exception:
            log.exception('item failed')  # keep the group alive
async with asyncio.TaskGroup() as tg:
    tg.create_task(resilient_worker(queue))
Defensive patterns

Strategy: validation

Validate before calling

null

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: tg.create_task() executed after another child raised an exception (or the group was cancelled) while __aexit__ is cancelling remaining tasks; typical with done-callbacks, external queues draining into create_task, or code racing the abort.

Common situations: Retry/reschedule logic triggered by a failing task that tries to re-enqueue work into the now-aborting group; a supervisor loop polling 'should I spawn more' that wins the race against an error-triggered shutdown; multiplexed workers fed from an external producer.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/d9e86cfea78511cb. Report an issue: GitHub.