{"record":{"id":"bc53f9ee51eb0279","repo":"scrapy/scrapy","slug":"engine-slot-not-assigned","errorCode":null,"errorMessage":"Engine slot not assigned","messagePattern":"Engine slot not assigned","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"scrapy/core/engine.py","lineNumber":440,"sourceCode":"\n        # downloader middleware can return requests (for example, redirects)\n        if isinstance(result, Request):\n            self.crawl(result)\n            return\n\n        try:\n            yield self.scraper.enqueue_scrape(result, request)\n        except Exception:\n            assert self.spider is not None\n            logger.error(\n                \"Error while enqueuing scrape\",\n                exc_info=True,\n                extra={\"spider\": self.spider},\n            )\n\n    def spider_is_idle(self) -> bool:\n        if self._slot is None:\n            raise RuntimeError(\"Engine slot not assigned\")\n        if not self.scraper.slot.is_idle():  # type: ignore[union-attr]\n            return False\n        if self.downloader.active:  # downloader has pending requests\n            return False\n        if self._start is not None:  # not all start requests are handled\n            return False\n        return not self._slot.scheduler.has_pending_requests()\n\n    def crawl(self, request: Request) -> None:\n        \"\"\"Inject the request into the spider <-> downloader pipeline\"\"\"\n        if self.spider is None:\n            raise RuntimeError(f\"No open spider to crawl: {request}\")\n        self._schedule_request(request)\n        self._slot.nextcall.schedule()  # type: ignore[union-attr]\n\n    def _schedule_request(self, request: Request) -> None:\n        request_scheduled_result = self.signals.send_catch_log(\n            signals.request_scheduled,","sourceCodeStart":422,"sourceCodeEnd":458,"githubUrl":"https://github.com/scrapy/scrapy/blob/06af687662112027b4482d31e2714a3cf280a91f/scrapy/core/engine.py#L422-L458","documentation":"ExecutionEngine.spider_is_idle() checks whether the current spider has finished all work, but the check reads engine._slot, which is only created by open_spider_async(). Calling it before a spider is opened (or after the slot was cleared) means there is no scheduler/nextcall to inspect, so the engine refuses to guess and raises RuntimeError. This is an internal-ordering guard, not a crawl logic error.","triggerScenarios":"Calling engine.spider_is_idle() before engine.open_spider_async() has run; calling it from custom code (extensions, tests) that grabbed the engine before the crawl started; calling it after close_spider_async() finished and tore down the slot.","commonSituations":"Custom extensions or scripts that poll the engine idle state; unit tests that instantiate ExecutionEngine directly and probe idle state; code racing the spider_opened signal.","solutions":["Only call spider_is_idle() after the spider_opened signal has fired (e.g. from a spider_opened handler onwards)","If writing tests, open the spider first via await engine.open_spider_async() before asserting idle state","Guard with 'if engine._slot is not None' or track spider_opened/spider_closed signals in your own code instead of polling the engine"],"exampleFix":"// before\nconst idle = engine.spider_is_idle();  // raises if slot not assigned\n\n# after (Python)\nfrom scrapy import signals\n\ndef on_opened(spider):\n    # safe from here on\n    print(engine.spider_is_idle())\n\ncrawler.signals.connect(on_opened, signal=signals.spider_opened)","handlingStrategy":"validation","validationCode":"# only probe idle state once the engine has a slot\nif engine._slot is None:\n    raise RuntimeError(\"engine not opened yet; cannot check idle\")\nidle = engine.spider_is_idle()","typeGuard":"def engine_has_slot(engine) -> bool:\n    return getattr(engine, \"_slot\", None) is not None","tryCatchPattern":null,"preventionTips":["Tie idle-state checks to the spider_opened..spider_closed window","Do not poll internal engine attributes from extensions; subscribe to spider_idle signal instead","In tests, always open the spider before asserting engine state"],"tags":["scrapy","engine","lifecycle","internal-api"],"backgroundTag":null,"analyzedSha":"06af687662112027b4482d31e2714a3cf280a91f","analyzedAt":"2026-08-15T00:21:48.041Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}