Hmbown/CodeWhale · error · anyhow::Error

Unable to compute next CRON run within 5 years

Error message

Unable to compute next CRON run within 5 years

What it means

Thrown by the CRON branch of next_after_in_timezone when a minute-by-minute search of MAX_CRON_SEARCH_MINUTES (60*24*366*5, five years including a leap-day budget) finds no local datetime matching the expression strictly after 'after'. With standard cron OR semantics between day-of-month and day-of-week, this requires the expression's firing dates to be absent from the entire window, e.g. a Feb-29-only schedule in a stretch without a leap day, or a search start near chrono's datetime limits.

Source

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

                    .and_then(|dt| dt.with_nanosecond(0))
                    .ok_or_else(|| anyhow::anyhow!("Unable to round CRON search start"))?
                    .checked_add_signed(Duration::minutes(1))
                    .ok_or_else(|| anyhow::anyhow!("CRON schedule exceeded its range"))?;

                for _ in 0..MAX_CRON_SEARCH_MINUTES {
                    if cron.matches(candidate_naive)
                        && let Some(candidate) = resolve_local_datetime(timezone, candidate_naive)
                    {
                        let candidate = candidate.with_timezone(&Utc);
                        if candidate > after {
                            return Ok(candidate);
                        }
                    }
                    candidate_naive = candidate_naive
                        .checked_add_signed(Duration::minutes(1))
                        .ok_or_else(|| anyhow::anyhow!("CRON schedule exceeded its range"))?;
                }
                bail!("Unable to compute next CRON run within 5 years");
            }
        }
    }

    fn next_after_slot(
        &self,
        slot: DateTime<Utc>,
        anchor_reference: DateTime<Utc>,
    ) -> Result<Option<DateTime<Utc>>> {
        match self {
            Self::Once { .. } => Ok(None),
            _ => self
                .next_after_with_anchor(slot, anchor_reference)
                .map(Some),
        }
    }
}

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Rewrite rare-date crons to fire on a date that exists every year, e.g. '0 0 28 2 *' with a leap-day check inside the task
  2. Drop over-restrictive day fields so at least one match exists per year
  3. Dry-run parse_rrule plus a next-run computation before activating the automation
  4. Catch the error and pause the automation rather than letting the scheduler retry every tick

Example fix

// before (may have no Feb 29 within the 5-year window)
rrule = "FREQ=CRON;EXPR=0 0 29 2 *"

// after (fires every Feb 28; the task itself checks for a real leap day)
rrule = "FREQ=CRON;EXPR=0 0 28 2 *"
Defensive patterns

Strategy: validation

Validate before calling

fn cron_next_run_plausible(expr: &str) -> Result<bool, anyhow::Error> {
    let rrule = format!("FREQ=CRON;EXPR={expr}");
    AutomationSchedule::parse_rrule(&rrule)?; // syntax + impossible-date checks
    // Feb-29-only expressions are the classic 5-year-window miss.
    let fields: Vec<&str> = expr.split_whitespace().collect();
    Ok(!(fields.len() == 5 && fields[2] == "29" && fields[3] == "2" && fields[4] == "*"))
}

Try / catch

match manager.update_automation(id, req) {
    Ok(rec) => { /* ... */ }
    Err(e) if e.to_string().contains("within 5 years") => {
        // Expression cannot fire inside the search horizon; pause instead of retrying.
        tracing::warn!(%e, "cron automation {id} never matches; pausing");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: An Active automation with FREQ=CRON;EXPR='0 0 29 2 *' evaluated between 2097 and 2103 (the 2100 century non-leap gap pushes the next Feb 29 more than five years out); or an 'after' timestamp close enough to NaiveDateTime's maximum that the 5-year window cannot complete.

Common situations: Copy-pasted rare-date cron expressions (Feb 29 maintenance jobs) on long-lived installations; tests pinned to far-future dates; schedules running across century boundaries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/aa13d89babe9b52b. Report an issue: GitHub.