{"record":{"id":"6ca40e4edafdd9a4","repo":"pytest-dev/pytest","slug":"cannot-exit-self-r-without-entering-first","errorCode":null,"errorMessage":"Cannot exit {self!r} without entering first","messagePattern":"Cannot exit (.+?) without entering first","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"src/_pytest/recwarn.py","lineNumber":255,"sourceCode":"        if self._entered:\n            __tracebackhide__ = True\n            raise RuntimeError(f\"Cannot enter {self!r} twice\")\n        _list = super().__enter__()\n        # record=True means it's None.\n        assert _list is not None\n        self._list = _list\n        warnings.simplefilter(\"always\")\n        return self\n\n    def __exit__(\n        self,\n        exc_type: type[BaseException] | None,\n        exc_val: BaseException | None,\n        exc_tb: TracebackType | None,\n    ) -> None:\n        if not self._entered:\n            __tracebackhide__ = True\n            raise RuntimeError(f\"Cannot exit {self!r} without entering first\")\n\n        super().__exit__(exc_type, exc_val, exc_tb)\n\n        # Built-in catch_warnings does not reset entered state so we do it\n        # manually here for this context manager to become reusable.\n        self._entered = False\n\n\n@final\nclass WarningsChecker(WarningsRecorder):\n    def __init__(\n        self,\n        expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning,\n        match_expr: str | re.Pattern[str] | None = None,\n        *,\n        _ispytest: bool = False,\n    ) -> None:\n        check_ispytest(_ispytest)","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/pytest-dev/pytest/blob/0d6fbdeffa57c796123f62f81f7dd370d9b7ecdc/src/_pytest/recwarn.py#L237-L273","documentation":"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.","triggerScenarios":"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.","commonSituations":"Exception in setup code that skips __enter__ but a finally block still calls __exit__; reuse of a closed recorder; buggy fixtures.","solutions":["Always use the with statement so __enter__/__exit__ stay paired.","If managing manually, set a flag and only call __exit__ when __enter__ succeeded.","Create a fresh recorder per use to avoid stale _entered state."],"exampleFix":"// before\nrecorder = WarningsRecorder(_ispytest=True)\ntry:\n    ...\nfinally:\n    recorder.__exit__(None, None, None)  # RuntimeError\n// after\nwith WarningsRecorder(_ispytest=True) as recorder:\n    ...","handlingStrategy":"validation","validationCode":"assert getattr(recorder, '_entered', False), f'cannot exit {recorder!r} before entering'","typeGuard":"def recorder_exitable(recorder) -> bool:\n    return getattr(recorder, '_entered', False)","tryCatchPattern":null,"preventionTips":["Always use the with statement so __enter__ precedes __exit__.","In try/finally, guard __exit__ with a flag set only after __enter__ succeeds.","Create a fresh recorder per use to avoid stale lifecycle state."],"tags":["pytest","warns","warningsrecorder","context-manager","lifecycle"],"backgroundTag":null,"analyzedSha":"0d6fbdeffa57c796123f62f81f7dd370d9b7ecdc","analyzedAt":"2026-08-11T20:52:36.969Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}