scrapy/scrapy · error · RuntimeError
Engine slot not assigned
Error message
Engine slot not assigned
What it means
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.
Source
Thrown at scrapy/core/engine.py:440
# downloader middleware can return requests (for example, redirects)
if isinstance(result, Request):
self.crawl(result)
return
try:
yield self.scraper.enqueue_scrape(result, request)
except Exception:
assert self.spider is not None
logger.error(
"Error while enqueuing scrape",
exc_info=True,
extra={"spider": self.spider},
)
def spider_is_idle(self) -> bool:
if self._slot is None:
raise RuntimeError("Engine slot not assigned")
if not self.scraper.slot.is_idle(): # type: ignore[union-attr]
return False
if self.downloader.active: # downloader has pending requests
return False
if self._start is not None: # not all start requests are handled
return False
return not self._slot.scheduler.has_pending_requests()
def crawl(self, request: Request) -> None:
"""Inject the request into the spider <-> downloader pipeline"""
if self.spider is None:
raise RuntimeError(f"No open spider to crawl: {request}")
self._schedule_request(request)
self._slot.nextcall.schedule() # type: ignore[union-attr]
def _schedule_request(self, request: Request) -> None:
request_scheduled_result = self.signals.send_catch_log(
signals.request_scheduled,View on GitHub (pinned to 06af687662)
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
Example fix
// before
const idle = engine.spider_is_idle(); // raises if slot not assigned
# after (Python)
from scrapy import signals
def on_opened(spider):
# safe from here on
print(engine.spider_is_idle())
crawler.signals.connect(on_opened, signal=signals.spider_opened) Defensive patterns
Strategy: validation
Validate before calling
# only probe idle state once the engine has a slot
if engine._slot is None:
raise RuntimeError("engine not opened yet; cannot check idle")
idle = engine.spider_is_idle() Type guard
def engine_has_slot(engine) -> bool:
return getattr(engine, "_slot", None) is not None Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- No open spider to crawl: {request}
- No free spider slot when opening {self.crawler.spider.name!r
- Spider not opened
- Engine already running
- Engine not running
AI-assisted analysis of scrapy/scrapy@06af687662 (2026-08-15).
Data as JSON: /api/errors/bc53f9ee51eb0279.
Report an issue: GitHub.