{"record":{"id":"35219af219ef8007","repo":"scrapy/scrapy","slug":"interval-must-be-greater-than-0","errorCode":null,"errorMessage":"Interval must be greater than 0","messagePattern":"Interval must be greater than 0","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"scrapy/utils/asyncio.py","lineNumber":172,"sourceCode":"\n    @property\n    def running(self) -> bool:\n        return self._start_time is not None\n\n    def start(self, interval: float, now: bool = True) -> None:\n        \"\"\"Start calling the function every *interval* seconds.\n\n        :param interval: The interval in seconds between calls.\n        :type interval: float\n\n        :param now: If ``True``, also call the function immediately.\n        :type now: bool\n        \"\"\"\n        if self.running:\n            raise RuntimeError(\"AsyncioLoopingCall already running\")\n\n        if interval <= 0:\n            raise ValueError(\"Interval must be greater than 0\")\n\n        self.interval = interval\n        self._start_time = time.monotonic()\n        if now:\n            self._call()\n        loop = asyncio.get_event_loop()\n        self._task = loop.create_task(self._loop())\n\n    def _to_sleep(self) -> float:\n        \"\"\"Return the time to sleep until the next call.\"\"\"\n        assert self.interval is not None\n        assert self._start_time is not None\n        now = time.monotonic()\n        running_for = now - self._start_time\n        return self.interval - (running_for % self.interval)\n\n    async def _loop(self) -> None:\n        \"\"\"Run an infinite loop that calls the function periodically.\"\"\"","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/scrapy/scrapy/blob/06af687662112027b4482d31e2714a3cf280a91f/scrapy/utils/asyncio.py#L154-L190","documentation":"AsyncioLoopingCall.start() validates the interval argument and raises ValueError when interval <= 0, because scheduling a periodic call at zero or negative periods is meaningless (it would spin the loop).","triggerScenarios":"Passing 0, a negative number, or a value computed to <= 0 (e.g. a settings-derived float that parsed to 0) as the interval argument of AsyncioLoopingCall.start().","commonSituations":"Reading an interval from settings where the key is missing and defaults to 0; float parsing of an empty string or invalid config value yielding 0; passing a timedelta object instead of seconds.","solutions":["Pass a positive interval in seconds, e.g. lc.start(30)","Validate/derive the interval defensively: interval = max(float(interval_setting), MIN_INTERVAL) or fail fast with a clear config error","Check that settings keys used for the interval are actually set (self.settings.getfloat('MY_INTERVAL', 60))"],"exampleFix":"# before\nlc.start(self.settings.getfloat('TICK_INTERVAL', 0))  # 0 -> ValueError\n\n# after\nlc.start(self.settings.getfloat('TICK_INTERVAL', 60))","handlingStrategy":"validation","validationCode":"interval = self.settings.getfloat('TICK_INTERVAL', 60)\nif interval <= 0:\n    raise ValueError(f'TICK_INTERVAL must be > 0, got {interval}')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always provide a positive default in settings.getfloat for interval keys","Validate externally loaded config numbers before passing them to start()"],"tags":["scrapy","asyncio","scheduling","validation","configuration"],"backgroundTag":null,"analyzedSha":"06af687662112027b4482d31e2714a3cf280a91f","analyzedAt":"2026-08-15T00:21:48.041Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}