python/cpython · error · RuntimeError

TaskGroup {self!r} cannot determine the parent task

Error message

TaskGroup {self!r} cannot determine the parent task

What it means

RuntimeError raised by TaskGroup.__aenter__ when tasks.current_task() returns None, i.e. the code entering the group is not running inside a Task. TaskGroup relies on a parent task to attach cancellation semantics (child failures cancel the parent), so entering it from bare callback/loop context is unsupported.

Source

Thrown at Lib/asyncio/taskgroups.py:64

        if self._errors:
            info.append(f'errors={len(self._errors)}')
        if self._aborting:
            info.append('cancelling')
        elif self._entered:
            info.append('entered')

        info_str = ' '.join(info)
        return f'<TaskGroup{info_str}>'

    async def __aenter__(self):
        if self._entered:
            raise RuntimeError(
                f"TaskGroup {self!r} has already been entered")
        if self._loop is None:
            self._loop = events.get_running_loop()
        self._parent_task = tasks.current_task(self._loop)
        if self._parent_task is None:
            raise RuntimeError(
                f'TaskGroup {self!r} cannot determine the parent task')
        self._entered = True
        if self._cancel_on_enter:
            self.cancel()

        return self

    async def __aexit__(self, et, exc, tb):
        tb = None
        try:
            return await self._aexit(et, exc)
        finally:
            # Exceptions are heavy objects that can have object
            # cycles (bad for GC); let's not keep a reference to
            # a bunch of them. It would be nicer to use a try/finally
            # in __aexit__ directly but that introduced some diff noise
            self._parent_task = None
            self._errors = None

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Ensure 'async with TaskGroup()' appears inside a coroutine that is scheduled as a Task (asyncio.run, loop.create_task, asyncio.create_task)
  2. Move TaskGroup usage out of callbacks: wrap the whole workflow in one top-level coroutine task
  3. For async generators needing structured concurrency, restructure so the group is entered and exited within a live task, not during aclose() from shutdown

Example fix

// before
async def worker():
    async with asyncio.TaskGroup() as tg: ...
loop.call_soon(lambda: asyncio.ensure_future(worker()))  # misdriven contexts can lack a current task

// after
async def worker():
    async with asyncio.TaskGroup() as tg: ...
asyncio.run(worker())  # always runs inside a Task
Defensive patterns

Strategy: validation

Validate before calling

null

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: Awaiting tg.__aenter__ from code driven outside a Task: e.g. directly inside a loop.run_until_complete chain is fine (it wraps in a Task) but code executed via loop.call_soon callbacks, signal handlers, or low-level manual coroutine stepping has no current task; also entering a TaskGroup during interpreter/loop shutdown when the task context is already gone.

Common situations: Async generators asynchat-style finalized via loop.shutdown_asyncgens after their task died; driving coroutines manually with coro.send(None) in tests; exotic embedding of asyncio in another runtime; calling __aenter__ from a thread other than the loop thread.

Related errors


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