{"record":{"id":"07d53ddaeb758428","repo":"D4Vinci/Scrapling","slug":"no-active-session-available","errorCode":null,"errorMessage":"No active session available.","messagePattern":"No active session available\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"scrapling/engines/static.py","lineNumber":244,"sourceCode":"        Perform an HTTP request using the configured session.\n        \"\"\"\n        stealth = self._stealth if stealth is None else stealth\n\n        selector_config = self._get_param(kwargs, \"selector_config\", self.selector_config) or self.selector_config\n        max_retries = self._get_param(kwargs, \"retries\", self._default_retries)\n        retry_delay = self._get_param(kwargs, \"retry_delay\", self._default_retry_delay)\n        static_proxy = kwargs.pop(\"proxy\", None)\n\n        session = self._curl_session\n        one_off_request = False\n        if session is _NO_SESSION and self.__enter__ is None:\n            # For usage inside FetcherClient\n            # It turns out `curl_cffi` caches impersonation state, so if you turned it off, then on then off, it won't be off on the last time.\n            session = CurlSession()\n            one_off_request = True\n\n        if not session:\n            raise RuntimeError(\"No active session available.\")  # pragma: no cover\n\n        try:\n            for attempt in range(max_retries):\n                proxy: Optional[ProxyType]\n                if self._proxy_rotator and static_proxy is None:\n                    proxy = self._proxy_rotator.get_proxy()\n                else:\n                    proxy = static_proxy or self._default_proxy\n\n                request_args = self._merge_request_args(stealth=stealth, proxy=proxy, **kwargs)\n                try:\n                    response = session.request(method, **request_args)\n                    assert response is not None\n                    result = ResponseFactory.from_http_request(response, selector_config, meta={\"proxy\": proxy})\n                    return result\n                except CurlError as e:  # pragma: no cover\n                    if attempt < max_retries - 1:\n                        # Now if the rotator is enabled, we will try again with the new proxy","sourceCodeStart":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/D4Vinci/Scrapling/blob/5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f/scrapling/engines/static.py#L226-L262","documentation":"Raised inside _SyncSessionLogic.fetch when no curl session is available to send the request with. The method first tries self._curl_session (set by __enter__), then falls back to creating a one-off CurlSession, but only when _curl_session is the _NO_SESSION sentinel and __enter__ has been nullified — i.e., only for the FetcherClient subclass. For a plain FetcherSession used outside its 'with' block, _curl_session stays None and falsy, so this RuntimeError fires. It is marked 'pragma: no cover' because it guards the misuse path.","triggerScenarios":"Creating FetcherSession(...) and calling session.get(url) / .fetch(...) without entering the 'with' block first; calling request methods after __exit__ has already closed and cleared the session; calling methods on a _SyncSessionLogic whose __enter__ raised midway so _curl_session was never assigned.","commonSituations":"Treating FetcherSession like the older stateless API (pre-context-manager versions allowed direct .get()); refactoring code from FetcherClient to FetcherSession and forgetting the 'with'; calling fetch from a helper that runs after the context block exited.","solutions":["Wrap usage in a context manager: 'with FetcherSession(...) as session: session.get(url)'.","If you don't want a session lifecycle, use FetcherClient (or AsyncFetcher's one-shot functions) which create one-off sessions automatically.","Ensure no code path calls request methods after the 'with' block ends (e.g., async callbacks or background threads outliving the block)."],"exampleFix":"# before\nsession = FetcherSession(stealth=True)\nresp = session.get('https://example.com')  # RuntimeError\n\n# after\nwith FetcherSession(stealth=True) as session:\n    resp = session.get('https://example.com')","handlingStrategy":"validation","validationCode":"def ensure_open(session):\n    # A usable session is either inside its context or a FetcherClient (one-off mode)\n    if not getattr(session, '_is_alive', False) and getattr(session, '_curl_session', None) is None:\n        raise RuntimeError('FetcherSession must be used inside \"with\"; or switch to FetcherClient')\n\n# usage\nwith FetcherSession() as session:\n    ensure_open(session)\n    session.get(url)","typeGuard":"def has_active_sync_session(session) -> bool:\n    return getattr(session, '_is_alive', False) is True and getattr(session, '_curl_session', None) is not None","tryCatchPattern":"try:\n    resp = session.get(url)\nexcept RuntimeError as e:\n    if 'No active session' in str(e):\n        with FetcherSession(**cfg) as s:\n            resp = s.get(url)\n    else:\n        raise","preventionTips":["Standardize on 'with FetcherSession(...) as session:' immediately after construction.","For stateless convenience, use FetcherClient / AsyncFetcher top-level functions.","Lint for request calls that sit outside the context block (e.g., returns of responses after the with ends)."],"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"}