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 = None

View on GitHub (pinned to 3e82f13042)

Solutions

  1. Fix the expression so at least one valid future datetime matches
  2. Validate programmatically with croniter and reject/repair at input time
  3. 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

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


AI-assisted analysis of HKUDS/DeepTutor@3e82f13042 (2026-08-27). Data as JSON: /api/errors/1b6cf88b46b79ea3. Report an issue: GitHub.