{"record":{"id":"240a8797cdeb2084","repo":"D4Vinci/Scrapling","slug":"cannot-exit-invalid-session","errorCode":null,"errorMessage":"Cannot exit invalid session","messagePattern":"Cannot exit invalid session","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"scrapling/engines/static.py","lineNumber":740,"sourceCode":"            config[\"selector_config\"] = self.selector_config\n            config[\"proxy_rotator\"] = self._proxy_rotator\n            self._client = _SyncSessionLogic(**config)\n            try:\n                result = self._client.__enter__()\n            except Exception:\n                self._client = None\n                raise\n            self._is_alive = True\n            return result\n        raise RuntimeError(\"This FetcherSession instance already has an active synchronous session.\")\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        if self._client is not None and isinstance(self._client, _SyncSessionLogic):\n            self._client.__exit__(exc_type, exc_val, exc_tb)\n            self._client = None\n            self._is_alive = False\n            return\n        raise RuntimeError(\"Cannot exit invalid session\")\n\n    async def __aenter__(self) -> _ASyncSessionLogic:\n        \"\"\"Creates and returns a new asynchronous Session.\"\"\"\n        if self._client is None:\n            # Use **vars(self) to avoid repeating all parameters\n            config = {k.replace(\"_default_\", \"\"): getattr(self, k) for k in self.__slots__ if k.startswith(\"_default\")}\n            config[\"stealthy_headers\"] = self._stealth\n            config[\"selector_config\"] = self.selector_config\n            config[\"proxy_rotator\"] = self._proxy_rotator\n            self._client = _ASyncSessionLogic(**config)\n            try:\n                result = await self._client.__aenter__()\n            except Exception:\n                self._client = None\n                raise\n            self._is_alive = True\n            return result\n        raise RuntimeError(\"This FetcherSession instance already has an active asynchronous session.\")","sourceCodeStart":722,"sourceCodeEnd":758,"githubUrl":"https://github.com/D4Vinci/Scrapling/blob/5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f/scrapling/engines/static.py#L722-L758","documentation":"Raised by FetcherSession.__exit__ when there is no active synchronous inner client to close: either self._client is None (nothing was entered, or it was already exited) or _client holds an _ASyncSessionLogic (an async session is open, which must be closed with __aexit__, not __exit__). It prevents silently closing the wrong session type.","triggerScenarios":"Calling __exit__ twice (double 'with' exit, or manual __exit__ after the context block); calling sync __exit__ when the session was opened with 'async with'; calling __exit__ on a fresh instance that never entered.","commonSituations":"A try/finally that manually calls session.__exit__() colliding with the context manager's own exit; mixed sync/async code closing the shared FetcherSession with the wrong protocol; cleanup code that runs after an earlier cleanup already reset _client to None.","solutions":["Let the 'with' statement manage exit; never call __exit__ manually for the same block.","Match protocols: close async sessions only with 'async with' / __aexit__.","Guard cleanup code with a check that the session is actually alive before exiting."],"exampleFix":"# before\nwith fs:\n    ...\nfs.__exit__(None, None, None)  # RuntimeError: already exited by 'with'\n\n# after\nwith fs:\n    ...\n# nothing else needed; 'with' already exited cleanly","handlingStrategy":"validation","validationCode":"def safe_sync_exit(fs, *exc):\n    client = getattr(fs, '_client', None)\n    from scrapling.engines.static import _SyncSessionLogic\n    if client is not None and isinstance(client, _SyncSessionLogic):\n        fs.__exit__(*exc)\n    # otherwise: nothing (or an async session) to close synchronously","typeGuard":"def can_sync_exit(fs) -> bool:\n    from scrapling.engines.static import _SyncSessionLogic\n    client = getattr(fs, '_client', None)\n    return client is not None and isinstance(client, _SyncSessionLogic)","tryCatchPattern":"try:\n    fs.__exit__(exc_type, exc_val, exc_tb)\nexcept RuntimeError as e:\n    if 'Cannot exit invalid session' in str(e):\n        pass  # nothing to close; session already exited or is async\n    else:\n        raise","preventionTips":["Never call __exit__ manually for blocks managed by 'with'.","Match exit protocol to enter protocol (sync/async).","In custom wrappers, track entered state and exit exactly once."],"tags":["session-lifecycle","static-fetcher","context-manager","sync"],"backgroundTag":null,"analyzedSha":"5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f","analyzedAt":"2026-08-14T22:23:09.440Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}