python/cpython · error · BaseExceptionGroup

unhandled errors in a TaskGroup

Error message

unhandled errors in a TaskGroup

What it means

BaseExceptionGroup('unhandled errors in a TaskGroup') is raised from TaskGroup.__aexit__ when one or more child tasks finished with exceptions that nothing else re-raised. It bundles every child failure (plus any interaction with an in-flight exception 'et'); a special case re-raises a sole child exception during GeneratorExit so async generators close cleanly.

Source

Thrown at Lib/asyncio/taskgroups.py:202

            # which will keep the cancel count stable.
            if self._parent_task.cancelling():
                self._parent_task.uncancel()
                self._parent_task.cancel()
            try:
                # If the *only* error is a GeneratorExit from the body
                # of the group, then instead of raising an
                # ExceptionGroup we raise GeneratorExit. This ensures
                # that async generators that use TaskGroup properly
                # swallow the exception on `aclose()` while ensuring
                # that no exceptions from subtasks are swallowed.
                if (
                    et is not None
                    and issubclass(et, GeneratorExit)
                    and len(self._errors) == 1
                ):
                    raise exc
                else:
                    raise BaseExceptionGroup(
                        'unhandled errors in a TaskGroup',
                        self._errors,
                    ) 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")

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Catch with except* (ExceptionGroup handling): 'except* ValueError as eg:' to handle each child failure type
  2. Add try/except inside task bodies for expected per-task errors so the group only propagates truly fatal ones
  3. Inspect eg.exceptions recursively to find root causes; log them before deciding on restart/abort

Example fix

// before
async with asyncio.TaskGroup() as tg:
    tg.create_task(fetch(url))

// after
try:
    async with asyncio.TaskGroup() as tg:
        tg.create_task(fetch(url))
except* ValueError as eg:
    for exc in eg.exceptions:
        log.warning('fetch failed: %r', exc)
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

try:
    async with asyncio.TaskGroup() as tg:
        tg.create_task(fetch(u)) for u in urls]
except* http.HTTPError as eg:
    log.warning('http failures: %r', eg.exceptions)
except* OSError as eg:
    raise  # fatal, abort the batch

Prevention

When it happens

Trigger: One or more tasks created via tg.create_task() raise before the 'async with' block exits; also combined failures during group abort, where sibling cancellations and the original error are collected together.

Common situations: Structured-concurrency fan-out where one worker raises (network error, bug) and you did not catch it inside the task body; several workers failing simultaneously (e.g. shared dependency down) producing a multi-exception group; nested TaskGroups producing nested groups.

Related errors


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