HKUDS/DeepTutor · error · ValueError
cron expression {schedule.expr!r} never fires
Error message
cron expression {schedule.expr!r} never fires What it means
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.
Source
Thrown at deeptutor/services/cron/service.py:173
raise ValueError("'at' schedules need a time")
if schedule.at_ms <= _now_ms():
raise ValueError("'at' time is in the past")
return
if schedule.kind == "every":
if not schedule.every_seconds or schedule.every_seconds < 30:
raise ValueError("'every' interval must be at least 30 seconds")
return
if schedule.kind == "cron":
if schedule.tz:
try:
from zoneinfo import ZoneInfo
ZoneInfo(schedule.tz)
except Exception:
raise ValueError(f"unknown timezone {schedule.tz!r}") from None
# Raises ValueError on bad/unsupported expressions.
if compute_next_run(schedule, _now_ms()) is None:
raise ValueError(f"cron expression {schedule.expr!r} never fires")
return
raise ValueError(f"unknown schedule kind {schedule.kind!r}")
class CronService:
"""Single-process job store + scheduler."""
def __init__(
self,
store_path: Path,
on_job: Callable[[CronJob], Awaitable[tuple[str, str | None]]] | None = None,
) -> None:
"""``on_job`` returns ``(status, error)`` with status ok/error/skipped."""
self.store_path = store_path
self.on_job = on_job
self._jobs: dict[str, CronJob] = {}
self._loaded = False
self._timer_task: asyncio.Task | None = NoneView on GitHub (pinned to 3e82f13042)
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
Example fix
# before sched = CronSchedule(kind="cron", expr="0 0 31 2 *") # Feb 31 never exists # after sched = CronSchedule(kind="cron", expr="0 0 28 2 *") # or for a one-off date: sched = CronSchedule(kind="at", at_ms=target_ms)
Defensive patterns
Strategy: validation
Validate before calling
from datetime import datetime
from croniter import croniter
def cron_fires(expr: str) -> bool:
try:
return croniter(expr, datetime.now()).get_next(datetime) is not None
except Exception:
return False
assert cron_fires(schedule.expr), f"{schedule.expr!r} never fires" Try / catch
try:
svc.add_job(schedule, ...)
except ValueError as e:
if "never fires" in str(e):
schedule = CronSchedule(kind="every", every_seconds=86400)
svc.add_job(schedule, ...)
else:
raise Prevention
- For one-off calendar dates use kind='at', not cron
- Validate generated cron strings with croniter get_next in CI
When it happens
Trigger: Expressions that parse but can never match a real datetime: impossible day/month combos like '0 0 31 2 *', passed to add_job.
Common situations: Hand-built date-like cron strings assembled from user form data (day=31, month=2); template-generated expressions combining incompatible fields.
Related errors
- invalid cron expression {schedule.expr!r}: {exc}
- 'at' schedules need a time
- 'at' time is in the past
- 'every' interval must be at least 30 seconds
- unknown timezone {schedule.tz!r}
AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27).
Data as JSON: /api/errors/1b6cf88b46b79ea3.
Report an issue: GitHub.