{"record":{"id":"1b6cf88b46b79ea3","repo":"HKUDS/DeepTutor","slug":"cron-expression-schedule-expr-r-never-fires","errorCode":null,"errorMessage":"cron expression {schedule.expr!r} never fires","messagePattern":"cron expression (.+?) never fires","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"deeptutor/services/cron/service.py","lineNumber":173,"sourceCode":"            raise ValueError(\"'at' schedules need a time\")\n        if schedule.at_ms <= _now_ms():\n            raise ValueError(\"'at' time is in the past\")\n        return\n    if schedule.kind == \"every\":\n        if not schedule.every_seconds or schedule.every_seconds < 30:\n            raise ValueError(\"'every' interval must be at least 30 seconds\")\n        return\n    if schedule.kind == \"cron\":\n        if schedule.tz:\n            try:\n                from zoneinfo import ZoneInfo\n\n                ZoneInfo(schedule.tz)\n            except Exception:\n                raise ValueError(f\"unknown timezone {schedule.tz!r}\") from None\n        # Raises ValueError on bad/unsupported expressions.\n        if compute_next_run(schedule, _now_ms()) is None:\n            raise ValueError(f\"cron expression {schedule.expr!r} never fires\")\n        return\n    raise ValueError(f\"unknown schedule kind {schedule.kind!r}\")\n\n\nclass CronService:\n    \"\"\"Single-process job store + scheduler.\"\"\"\n\n    def __init__(\n        self,\n        store_path: Path,\n        on_job: Callable[[CronJob], Awaitable[tuple[str, str | None]]] | None = None,\n    ) -> None:\n        \"\"\"``on_job`` returns ``(status, error)`` with status ok/error/skipped.\"\"\"\n        self.store_path = store_path\n        self.on_job = on_job\n        self._jobs: dict[str, CronJob] = {}\n        self._loaded = False\n        self._timer_task: asyncio.Task | None = None","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/HKUDS/DeepTutor/blob/3e82f130422a813cdd73c10b21a44e9325f5821a/deeptutor/services/cron/service.py#L155-L191","documentation":"For 'cron' schedules, validate_schedule calls compute_next_run and treats a None return as an expression that never fires — e.g. an impossible date like '0 0 31 2 *' (Feb 31). croniter finds no next occurrence, so the job is rejected as permanently dormant. Distinct from a syntactically invalid expression.","triggerScenarios":"Expressions that parse but can never match a real datetime: impossible day/month combos like '0 0 31 2 *', passed to add_job.","commonSituations":"Hand-built date-like cron strings assembled from user form data (day=31, month=2); template-generated expressions combining incompatible fields.","solutions":["Fix the expression so at least one valid future datetime matches","Validate programmatically with croniter and reject/repair at input time","Prefer 'at' one-shot schedules for specific calendar dates instead of cron expressions"],"exampleFix":"# before\nsched = CronSchedule(kind=\"cron\", expr=\"0 0 31 2 *\")  # Feb 31 never exists\n# after\nsched = CronSchedule(kind=\"cron\", expr=\"0 0 28 2 *\")\n# or for a one-off date:\nsched = CronSchedule(kind=\"at\", at_ms=target_ms)","handlingStrategy":"validation","validationCode":"from datetime import datetime\nfrom croniter import croniter\n\ndef cron_fires(expr: str) -> bool:\n    try:\n        return croniter(expr, datetime.now()).get_next(datetime) is not None\n    except Exception:\n        return False\n\nassert cron_fires(schedule.expr), f\"{schedule.expr!r} never fires\"","typeGuard":null,"tryCatchPattern":"try:\n    svc.add_job(schedule, ...)\nexcept ValueError as e:\n    if \"never fires\" in str(e):\n        schedule = CronSchedule(kind=\"every\", every_seconds=86400)\n        svc.add_job(schedule, ...)\n    else:\n        raise","preventionTips":["For one-off calendar dates use kind='at', not cron","Validate generated cron strings with croniter get_next in CI"],"tags":["cron","validation","logic-error"],"backgroundTag":"cron-expression-invalid","analyzedSha":"3e82f130422a813cdd73c10b21a44e9325f5821a","analyzedAt":"2026-08-27T06:57:25.364Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}