D4Vinci/Scrapling · error · RuntimeError

Context manager has been closed

Error message

Context manager has been closed

What it means

Raised by StealthySession.fetch() when the session's `_is_alive` flag is False, i.e. the underlying browser context was never started or has already been closed. fetch() can only run while the session context manager is open.

Source

Thrown at scrapling/engines/_browsers/_stealth.py:210

        :param page_setup: A function that takes the `page` object, runs before navigation. Use it to register event listeners or routes that must be set up before the page loads.
        :param extra_headers: A dictionary of extra headers to add to the request. _The referer set by `google_search` takes priority over the referer set here if used together._
        :param disable_resources: Drop requests for unnecessary resources for a speed boost.
            Requests dropped are of type `font`, `image`, `media`, `beacon`, `object`, `imageset`, `texttrack`, `websocket`, `csp_report`, and `stylesheet`.
        :param blocked_domains: A set of domain names to block requests to. Subdomains are also matched (e.g., ``"example.com"`` blocks ``"sub.example.com"`` too).
        :param wait_selector: Wait for a specific CSS selector to be in a specific state.
        :param wait_selector_state: The state to wait for the selector given with `wait_selector`. The default state is `attached`.
        :param network_idle: Wait for the page until there are no network connections for at least 500 ms.
        :param load_dom: Enabled by default, wait for all JavaScript on page(s) to fully load and execute.
        :param solve_cloudflare: Solves all types of the Cloudflare's Turnstile/Interstitial challenges before returning the response to you.
        :param selector_config: The arguments that will be passed in the end while creating the final Selector's class.
        :param proxy: Static proxy to override rotator and session proxy. A new browser context will be created and used with it.
        :return: A `Response` object.
        """
        static_proxy = kwargs.pop("proxy", None)

        params = _validate(kwargs, self, StealthConfig)
        if not self._is_alive:  # pragma: no cover
            raise RuntimeError("Context manager has been closed")

        request_headers_keys = {h.lower() for h in params.extra_headers.keys()} if params.extra_headers else set()
        referer = (
            "https://www.google.com/" if (params.google_search and "referer" not in request_headers_keys) else None
        )

        for attempt in range(self._config.retries):
            proxy: Optional[ProxyType] = None
            if self._config.proxy_rotator and static_proxy is None:
                proxy = self._config.proxy_rotator.get_proxy()
            else:
                proxy = static_proxy

            with self._page_generator(
                params.timeout, params.extra_headers, params.disable_resources, proxy, params.blocked_domains
            ) as page_info:
                final_response: List = [None]
                xhr_captured: List = []

View on GitHub (pinned to 5d213a2d47)

Solutions

  1. Move all fetch() calls inside the `with StealthySession(...) as session:` block
  2. Ensure start() succeeded before fetching and that no prior error closed the session
  3. For long-lived usage, keep the context manager open for the whole scraping run and close it in a finally block

Example fix

// before
with StealthySession() as s:
    pass
resp = s.fetch('https://example.com')  # RuntimeError

// after
with StealthySession() as s:
    resp = s.fetch('https://example.com')
Defensive patterns

Strategy: validation

Validate before calling

if not session._is_alive:
    raise RuntimeError('session closed; reopen before fetching')
resp = session.fetch(url)

Type guard

def can_fetch(s) -> bool:
    return bool(getattr(s, '_is_alive', False))

Try / catch

try:
    resp = session.fetch(url)
except RuntimeError as e:
    if 'closed' in str(e):
        # reopen a new session and retry once
        with StealthySession() as s2:
            resp = s2.fetch(url)
    else:
        raise

Prevention

When it happens

Trigger: Calling fetch() after the `with StealthySession(...)` block exited, calling fetch() before start(), or after stop()/an internal crash tore down the browser.

Common situations: Storing the session and calling fetch() later in a different function/scope after the context manager closed, or an earlier exception in __exit__ leaving the session dead.

Related errors


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