D4Vinci/Scrapling · error · RuntimeError

Cannot exit invalid session

Error message

Cannot exit invalid session

What it means

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.

Source

Thrown at scrapling/engines/static.py:740

            config["selector_config"] = self.selector_config
            config["proxy_rotator"] = self._proxy_rotator
            self._client = _SyncSessionLogic(**config)
            try:
                result = self._client.__enter__()
            except Exception:
                self._client = None
                raise
            self._is_alive = True
            return result
        raise RuntimeError("This FetcherSession instance already has an active synchronous session.")

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self._client is not None and isinstance(self._client, _SyncSessionLogic):
            self._client.__exit__(exc_type, exc_val, exc_tb)
            self._client = None
            self._is_alive = False
            return
        raise RuntimeError("Cannot exit invalid session")

    async def __aenter__(self) -> _ASyncSessionLogic:
        """Creates and returns a new asynchronous Session."""
        if self._client is None:
            # Use **vars(self) to avoid repeating all parameters
            config = {k.replace("_default_", ""): getattr(self, k) for k in self.__slots__ if k.startswith("_default")}
            config["stealthy_headers"] = self._stealth
            config["selector_config"] = self.selector_config
            config["proxy_rotator"] = self._proxy_rotator
            self._client = _ASyncSessionLogic(**config)
            try:
                result = await self._client.__aenter__()
            except Exception:
                self._client = None
                raise
            self._is_alive = True
            return result
        raise RuntimeError("This FetcherSession instance already has an active asynchronous session.")

View on GitHub (pinned to 5d213a2d47)

Solutions

  1. Let the 'with' statement manage exit; never call __exit__ manually for the same block.
  2. Match protocols: close async sessions only with 'async with' / __aexit__.
  3. Guard cleanup code with a check that the session is actually alive before exiting.

Example fix

# before
with fs:
    ...
fs.__exit__(None, None, None)  # RuntimeError: already exited by 'with'

# after
with fs:
    ...
# nothing else needed; 'with' already exited cleanly
Defensive patterns

Strategy: validation

Validate before calling

def safe_sync_exit(fs, *exc):
    client = getattr(fs, '_client', None)
    from scrapling.engines.static import _SyncSessionLogic
    if client is not None and isinstance(client, _SyncSessionLogic):
        fs.__exit__(*exc)
    # otherwise: nothing (or an async session) to close synchronously

Type guard

def can_sync_exit(fs) -> bool:
    from scrapling.engines.static import _SyncSessionLogic
    client = getattr(fs, '_client', None)
    return client is not None and isinstance(client, _SyncSessionLogic)

Try / catch

try:
    fs.__exit__(exc_type, exc_val, exc_tb)
except RuntimeError as e:
    if 'Cannot exit invalid session' in str(e):
        pass  # nothing to close; session already exited or is async
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of D4Vinci/Scrapling@5d213a2d47 (2026-08-14). Data as JSON: /api/errors/240a8797cdeb2084. Report an issue: GitHub.