{"record":{"id":"aa13d89babe9b52b","repo":"Hmbown/CodeWhale","slug":"unable-to-compute-next-cron-run-within-5-years","errorCode":null,"errorMessage":"Unable to compute next CRON run within 5 years","messagePattern":"Unable to compute next CRON run within 5 years","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/automation_manager.rs","lineNumber":547,"sourceCode":"                    .and_then(|dt| dt.with_nanosecond(0))\n                    .ok_or_else(|| anyhow::anyhow!(\"Unable to round CRON search start\"))?\n                    .checked_add_signed(Duration::minutes(1))\n                    .ok_or_else(|| anyhow::anyhow!(\"CRON schedule exceeded its range\"))?;\n\n                for _ in 0..MAX_CRON_SEARCH_MINUTES {\n                    if cron.matches(candidate_naive)\n                        && let Some(candidate) = resolve_local_datetime(timezone, candidate_naive)\n                    {\n                        let candidate = candidate.with_timezone(&Utc);\n                        if candidate > after {\n                            return Ok(candidate);\n                        }\n                    }\n                    candidate_naive = candidate_naive\n                        .checked_add_signed(Duration::minutes(1))\n                        .ok_or_else(|| anyhow::anyhow!(\"CRON schedule exceeded its range\"))?;\n                }\n                bail!(\"Unable to compute next CRON run within 5 years\");\n            }\n        }\n    }\n\n    fn next_after_slot(\n        &self,\n        slot: DateTime<Utc>,\n        anchor_reference: DateTime<Utc>,\n    ) -> Result<Option<DateTime<Utc>>> {\n        match self {\n            Self::Once { .. } => Ok(None),\n            _ => self\n                .next_after_with_anchor(slot, anchor_reference)\n                .map(Some),\n        }\n    }\n}\n","sourceCodeStart":529,"sourceCodeEnd":565,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/automation_manager.rs#L529-L565","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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","Drop over-restrictive day fields so at least one match exists per year","Dry-run parse_rrule plus a next-run computation before activating the automation","Catch the error and pause the automation rather than letting the scheduler retry every tick"],"exampleFix":"// before (may have no Feb 29 within the 5-year window)\nrrule = \"FREQ=CRON;EXPR=0 0 29 2 *\"\n\n// after (fires every Feb 28; the task itself checks for a real leap day)\nrrule = \"FREQ=CRON;EXPR=0 0 28 2 *\"","handlingStrategy":"validation","validationCode":"fn cron_next_run_plausible(expr: &str) -> Result<bool, anyhow::Error> {\n    let rrule = format!(\"FREQ=CRON;EXPR={expr}\");\n    AutomationSchedule::parse_rrule(&rrule)?; // syntax + impossible-date checks\n    // Feb-29-only expressions are the classic 5-year-window miss.\n    let fields: Vec<&str> = expr.split_whitespace().collect();\n    Ok(!(fields.len() == 5 && fields[2] == \"29\" && fields[3] == \"2\" && fields[4] == \"*\"))\n}","typeGuard":null,"tryCatchPattern":"match manager.update_automation(id, req) {\n    Ok(rec) => { /* ... */ }\n    Err(e) if e.to_string().contains(\"within 5 years\") => {\n        // Expression cannot fire inside the search horizon; pause instead of retrying.\n        tracing::warn!(%e, \"cron automation {id} never matches; pausing\");\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Avoid cron expressions whose only firing dates are rarer than once a year","Remember day-of-month and day-of-week combine with OR, so rarity must come from the month/day fields","Test rare-date schedules by computing the next run before activation","Pause rather than delete unreachable automations so config is preserved for repair"],"tags":["rust","cron","schedule","leap-year","automation"],"backgroundTag":"cron-expression-never-matches","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}