pytest-dev/pytest · error · BaseExceptionGroup

errors while tearing down fixture "{self.argname}" of {node}

Error message

errors while tearing down fixture "{self.argname}" of {node}

What it means

Raised by `FixtureDef.finish` when two or more finalizers/teardown steps of a single fixture raise during teardown. pytest collects all exceptions and re-raises them wrapped in a `BaseExceptionGroup` (the message names the fixture argname and the node) so no teardown error is swallowed. A single exception is re-raised unwrapped.

Source

Thrown at src/_pytest/fixtures.py:1230

        exceptions: list[BaseException] = []
        while self._finalizers:
            fin = self._finalizers.pop()
            try:
                fin()
            except BaseException as e:
                exceptions.append(e)
        node = request.node
        # Even if finalization fails, we invalidate the cached fixture
        # value and remove all finalizers because they may be bound methods
        # which will keep instances alive.
        self.cached_result = None
        self._finalizers.clear()
        if len(exceptions) == 1:
            raise exceptions[0]
        elif len(exceptions) > 1:
            msg = f'errors while tearing down fixture "{self.argname}" of {node}'
            raise BaseExceptionGroup(msg, exceptions[::-1])

    def execute(self, request: SubRequest) -> FixtureValue:
        """Return the value of this fixture, executing it if not cached."""
        # Ensure that the dependent fixtures requested by this fixture are loaded.
        # This needs to be done before checking if we have a cached value, since
        # if a dependent fixture has their cache invalidated, e.g. due to
        # parametrization, they finalize themselves and fixtures depending on it
        # (which will likely include this fixture) setting `self.cached_result = None`.
        # See #4871
        requested_fixtures_that_should_finalize_us = []
        for argname in self.argnames:
            fixturedef = request._get_active_fixturedef(argname)
            # Saves requested fixtures in a list so we later can add our finalizer
            # to them, ensuring that if a requested fixture gets torn down we get torn
            # down first. This is generally handled by SetupState, but still currently
            # needed when this fixture is not parametrized but depends on a parametrized
            # fixture.
            requested_fixtures_that_should_finalize_us.append(fixturedef)

View on GitHub (pinned to 98b357f69e)

Solutions

  1. Make teardown code defensive: wrap each cleanup step in try/except and log instead of raising where safe.
  2. Use `except*` (exception groups) at the test runner level, or `pytest.raises(BaseExceptionGroup)` in tests.
  3. Identify which finalizers failed from the message and fix the root cause of each.

Example fix

// before
@pytest.fixture
def db():
    conn = connect()
    yield conn
    conn.close()          # may raise
    conn.log_logout()     # also may raise -> group
// after
@pytest.fixture
def db():
    conn = connect()
    yield conn
    try:
        conn.close()
    finally:
        try:
            conn.log_logout()
        except Exception:
            pass
Defensive patterns

Strategy: try-catch

Try / catch

try:
    yield resource
except BaseExceptionGroup as eg:
    for e in eg.exceptions:
        log.warning("teardown error: %r", e)

Prevention

When it happens

Trigger: A fixture whose teardown (after yield) plus one or more of its dependent finalizers both raise. Common when the post-yield cleanup raises and an `addfinalizer` callback registered elsewhere also raises.

Common situations: Teardown of a resource fails (connection already closed) and a second finalizer also fails. CI flakiness where cleanup hits a transient error. Migrating to Python 3.11+ where ExceptionGroup surfaces.

Related errors


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