pytest-dev/pytest · warning · ExceptionGroup

multiple unraisable exception warnings

Error message

multiple unraisable exception warnings

What it means

Raised as an ExceptionGroup during session teardown when more than one unraisable exception was captured over the session. pytest installs sys.unraisablehook to record exceptions Python could not raise anywhere (typically from __del__ methods, generator finalization, or C-level callbacks during GC); at teardown collect_unraisable re-emits each as PytestUnraisableExceptionWarning, and several get bundled into this group. A forced gc.collect (5 passes on PyPy, 1 on CPython) is run first to flush pending finalizers.

Source

Thrown at src/_pytest/unraisableexception.py:81

                continue

            msg = meta.msg
            try:
                warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))
            except pytest.PytestUnraisableExceptionWarning as e:
                # This except happens when the warning is treated as an error (e.g. `-Werror`).
                if meta.exc_value is not None:
                    # Exceptions have a better way to show the traceback, but
                    # warnings do not, so hide the traceback from the msg and
                    # set the cause so the traceback shows up in the right place.
                    e.args = (meta.cause_msg,)
                    e.__cause__ = meta.exc_value
                errors.append(e)

        if len(errors) == 1:
            raise errors[0]
        if errors:
            raise ExceptionGroup("multiple unraisable exception warnings", errors)
    finally:
        del errors, meta, hook_error


def cleanup(
    *, config: Config, prev_hook: Callable[[sys.UnraisableHookArgs], object]
) -> None:
    # On PyPy, objects (e.g. coroutines) can survive GC rounds because executing
    # their __del__ can resurrect them. The Trio project determined experimentally
    # that 5 passes are needed on PyPy to flush everything. On CPython, reference
    # counting handles most cleanup immediately, so 1 pass is sufficient.
    _default_gc_collect_iterations = 5 if sys.implementation.name == "pypy" else 1
    gc_collect_iterations = config.stash.get(
        gc_collect_iterations_key, _default_gc_collect_iterations
    )
    try:
        try:
            gc_collect_harder(gc_collect_iterations)

View on GitHub (pinned to 98b357f69e)

Solutions

  1. Run with `pytest -W error::pytest.PytestUnraisableExceptionWarning` to surface the first unraisable as a real failure and locate its traceback.
  2. Close resources explicitly (context managers, asyncio.run, contextlib.aclosing) instead of relying on GC.
  3. Fix __del__ methods to swallow/log exceptions; never let __del__ raise.
  4. Disable -Werror for this category if the warnings are benign in CI: `pytest -W default::pytest.PytestUnraisableExceptionWarning`.

Example fix

// before
class Conn:
    def __del__(self):
        self.sock.close()  # raises if already closed -> unraisable

// after
class Conn:
    def close(self):
        try: self.sock.close()
        except OSError: pass
    def __del__(self):
        try: self.close()
        except Exception: pass
Defensive patterns

Strategy: try-catch

Validate before calling

import gc, sys, warnings

def flush_unraisable():
    captured = []
    prev = sys.unraisablehook
    sys.unraisablehook = lambda a: captured.append(a)
    gc.collect()
    sys.unraisablehook = prev
    if captured:
        warnings.warn(f"{len(captured)} unraisable exceptions pending")
    return captured

Try / catch

try:
    run_test()
except* pytest.PytestUnraisableExceptionWarning as eg:
    for e in eg.exceptions:
        diagnose_unraisable(e)

Prevention

When it happens

Trigger: Test code (or library code under test) defines __del__ that raises, leaks generators/coroutines that fail on close, uses C extensions that write to unraisablehook, or leaves file handles / sockets to be GC'd with errors. Multiple such events across the session produce the group at teardown.

Common situations: Async coroutines not awaited/closed (ResourceWarning + __del__ errors); objects with broken __del__ in app code; native modules (numpy, hdf5, db drivers) emitting unraisable warnings on shutdown; running with -Werror which converts each warning into a hard exception.

Related errors


AI-assisted analysis of pytest-dev/pytest@98b357f69e (2026-08-04). Data as JSON: /data/errors/448eb3aec771d3ab.json. Report an issue: GitHub.