HKUDS/DeepTutor · error · ValueError

invalid cron expression

Error message

invalid cron expression {schedule.expr!r}: {exc}

What it means

compute_next_run wraps croniter evaluation in a broad except and re-raises any parsing/evaluation failure as ValueError including the offending expression and underlying cause. Invalid or unsupportable cron strings fail fast at validation time rather than at fire time.

Solutions

  1. Fix the expression to standard 5-field cron syntax (minute hour day month weekday)
  2. Validate user input with croniter.croniter(expr, datetime.now()) before calling add_job
  3. Log schedule.expr on failure to identify which job config is broken

Example fix

# before
expr = "0 9 * * *" + user_input  # concatenated garbage
job = svc.add_job(CronSchedule(kind="cron", expr=expr), ...)
# after
from croniter import croniter
try:
    croniter(user_input, datetime.now())
    expr = user_input
except Exception:
    expr = "0 9 * * *"
job = svc.add_job(CronSchedule(kind="cron", expr=expr), ...)
Defensive patterns

Strategy: validation

Validate before calling

from datetime import datetime
from croniter import croniter

def valid_cron(expr: str) -> bool:
    try:
        croniter(expr, datetime.now())
        return True
    except Exception:
        return False

assert valid_cron(schedule.expr), f"bad cron: {schedule.expr!r}"

Try / catch

try:
    svc.add_job(schedule, ...)
except ValueError as e:
    if "invalid cron expression" in str(e):
        log.warning("rejecting cron %r", schedule.expr)
        return error_response(str(e))
    raise

Prevention

When it happens

Trigger: Passing a malformed expression like CronSchedule(kind="cron", expr="99 * * *") or "* * *" (wrong field count) to add_job or validate_schedule.

Common situations: Typos in hand-written cron strings; converting 5-field to 6-field/Quartz syntax; unsanitized user-supplied cron input from forms or chat commands.

Related errors


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

Appendix: source

Thrown at deeptutor/services/cron/service.py:146

        return now_ms + schedule.every_seconds * 1000

    if schedule.kind == "cron" and schedule.expr:
        try:
            from zoneinfo import ZoneInfo

            from croniter import croniter

            tz = ZoneInfo(schedule.tz) if schedule.tz else datetime.now().astimezone().tzinfo
            base = datetime.fromtimestamp(now_ms / 1000, tz=tz)
            next_dt = croniter(schedule.expr, base).get_next(datetime)
            return int(next_dt.timestamp() * 1000)
        except ImportError:
            raise ValueError(
                "cron expressions need the 'croniter' package — "
                "use an 'every' or 'at' schedule instead"
            ) from None
        except Exception as exc:
            raise ValueError(f"invalid cron expression {schedule.expr!r}: {exc}") from None

    return None


def validate_schedule(schedule: CronSchedule) -> None:
    """Reject schedules that could never run (raises ValueError)."""
    if schedule.kind == "at":
        if not schedule.at_ms:
            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:

View on GitHub (pinned to 3e82f13042)