Hmbown/CodeWhale · error · anyhow::Error

CRON EXPR day-of-month/month combination can never occur

Error message

CRON EXPR day-of-month/month combination can never occur

What it means

The cron parser validates that a schedule's day-of-month field can actually match at least one day of the given month, including leap years (days_in_month(2024, month) is used as the leap-year upper bound). If every listed day-of-month exceeds the maximum possible length of the specified month(s), the combination can never fire and parsing is rejected. This catches schedules like '0 0 31 2 *' (Feb 31) that would otherwise silently never run.

Solutions

  1. Change the day-of-month to a value that exists in the specified month(s), e.g. '0 0 29 2 *' for leap-year-only Feb
  2. Widen the month field so the day exists, e.g. '0 0 31 1,3,5,7,8,10,12 *'
  3. Use a step or range that includes at least one valid day, e.g. '0 0 */30 2 *' (30 does not exist in Feb either — use 1-28 instead)

Example fix

// before
schedule = "0 0 31 2 *"; // never occurs
// after
schedule = "0 0 28 2 *"; // or 29 for leap-year-only
Defensive patterns

Strategy: validation

Validate before calling

fn day_valid_for_month(day: u32, month: u32) -> bool {
    let leap = days_in_month(2024, month);
    let common = days_in_month(2023, month);
    day <= leap || day <= common
}
assert!(day_valid_for_month(29, 2), "Feb 29 is the Feb maximum");

Prevention

When it happens

Trigger: Calling automation save/validation with a cron expression whose day-of-month entries (e.g. 30, 31) all exceed the number of days in the month(s) given in the month field, e.g. '0 0 31 2 *' or '0 0 30 2,4,6 *' where even leap-year Feb (29) is exceeded.

Common situations: Hand-written cron strings copy-pasted from documentation examples ('0 0 31 2 *' is a classic snippet for 'never runs'); assuming cron clamps day 31 to the last day of shorter months as some schedulers do; typo like month 02 with day 30.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/76780cf165d0abd7. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/automation_manager.rs:807

            day_of_month || weekday
        }
    }

    fn validate_date_space(&self) -> Result<()> {
        if self.day_of_month.is_wildcard {
            return Ok(());
        }
        let months = self.month.values();
        let days = self.day_of_month.values();
        let valid = months.iter().copied().any(|month| {
            let common = days_in_month(2025, month);
            let leap = days_in_month(2024, month);
            days.iter().copied().any(|day| day <= common || day <= leap)
        });
        if valid {
            Ok(())
        } else {
            bail!("CRON EXPR day-of-month/month combination can never occur")
        }
    }
}

#[derive(Debug, Clone)]
struct CronField {
    values: Vec<u32>,
    is_wildcard: bool,
}

impl CronField {
    fn parse(raw: &str, min: u32, max: u32, names: CronNameMap, field_name: &str) -> Result<Self> {
        let trimmed = raw.trim();
        if trimmed.is_empty() {
            bail!("CRON {field_name} field must not be empty");
        }
        let mut values = Vec::new();
        let is_wildcard = trimmed == "*";

View on GitHub (pinned to 73e0f67d83)