pytest-dev/pytest · error · RuntimeError

Cannot exit without entering first

Error message

Cannot exit {self!r} without entering first

What it means

Raised by WarningsRecorder.__exit__ when exit is called before enter. The recorder requires its setup (catch_warnings.__enter__) to have run to restore state cleanly; exiting without entering would leave the warnings module in an inconsistent state. The _entered guard detects the missing setup and raises RuntimeError.

Solutions

  1. Always use the with statement so __enter__/__exit__ stay paired.
  2. If managing manually, set a flag and only call __exit__ when __enter__ succeeded.
  3. Create a fresh recorder per use to avoid stale _entered state.

Example fix

// before
recorder = WarningsRecorder(_ispytest=True)
try:
    ...
finally:
    recorder.__exit__(None, None, None)  # RuntimeError
// after
with WarningsRecorder(_ispytest=True) as recorder:
    ...
Defensive patterns

Strategy: validation

Validate before calling

assert getattr(recorder, '_entered', False), f'cannot exit {recorder!r} before entering'

Type guard

def recorder_exitable(recorder) -> bool:
    return getattr(recorder, '_entered', False)

Prevention

When it happens

Trigger: Calling recorder.__exit__(None, None, None) without a prior __enter__; a try/finally that calls __exit__ on an un-entered recorder; miswired custom context managers that forward __exit__ unconditionally.

Common situations: Exception in setup code that skips __enter__ but a finally block still calls __exit__; reuse of a closed recorder; buggy fixtures.

Related errors


AI-assisted analysis of pytest-dev/pytest@0d6fbdeffa (2026-08-11). Data as JSON: /api/errors/6ca40e4edafdd9a4. Report an issue: GitHub.

Appendix: source

Thrown at src/_pytest/recwarn.py:255

        if self._entered:
            __tracebackhide__ = True
            raise RuntimeError(f"Cannot enter {self!r} twice")
        _list = super().__enter__()
        # record=True means it's None.
        assert _list is not None
        self._list = _list
        warnings.simplefilter("always")
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        if not self._entered:
            __tracebackhide__ = True
            raise RuntimeError(f"Cannot exit {self!r} without entering first")

        super().__exit__(exc_type, exc_val, exc_tb)

        # Built-in catch_warnings does not reset entered state so we do it
        # manually here for this context manager to become reusable.
        self._entered = False


@final
class WarningsChecker(WarningsRecorder):
    def __init__(
        self,
        expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning,
        match_expr: str | re.Pattern[str] | None = None,
        *,
        _ispytest: bool = False,
    ) -> None:
        check_ispytest(_ispytest)

View on GitHub (pinned to 0d6fbdeffa)