Hmbown/CodeWhale · error · anyhow::Error

Unable to compute next WEEKLY run

Error message

Unable to compute next WEEKLY run

What it means

Thrown by the WEEKLY branch of next_after_in_timezone after scanning 15 consecutive days (day_offset 0..15) without finding a BYDAY/BYHOUR/BYMINUTE wall time that resolves to a real local instant strictly after 'after'. Any non-empty BYDAY recurs within 7 days, so through parse_rrule (which enforces non-empty WEEKLY BYDAY) this is a defensive exhaustion guard; it becomes reachable only when every matching wall time in the window fails to resolve in local time (DST spring-forward gap) or the schedule was constructed directly with an empty byday vec.

Source

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

                byday,
                byhour,
                byminute,
            } => {
                for day_offset in 0..15 {
                    let date = local_after.date_naive() + Duration::days(i64::from(day_offset));
                    if !byday.contains(&date.weekday()) {
                        continue;
                    }
                    let Some(candidate_naive) = date.and_hms_opt(*byhour, *byminute, 0) else {
                        continue;
                    };
                    if let Some(candidate) = resolve_local_datetime(timezone, candidate_naive)
                        && candidate.with_timezone(&Utc) > after
                    {
                        return Ok(candidate.with_timezone(&Utc));
                    }
                }
                bail!("Unable to compute next WEEKLY run");
            }
            Self::Cron { expr } => {
                let cron = ParsedCronExpr::parse(expr)?;
                let mut candidate_naive = local_after
                    .naive_local()
                    .with_second(0)
                    .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);

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. If constructing Weekly directly, reject an empty byday before computing the next run
  2. Route schedule creation through parse_rrule so WEEKLY BYDAY non-emptiness is enforced
  3. Shift BYHOUR/BYMINUTE away from the local DST gap hour if the error appears seasonally
  4. If reached via parse_rrule, report it as a bug with the timezone and rrule

Example fix

// before (direct construction with no days)
let schedule = AutomationSchedule::Weekly { byday: vec![], byhour: 9, byminute: 0 };

// after (validate before use)
if byday.is_empty() {
    anyhow::bail!("WEEKLY schedule needs at least one BYDAY day");
}
Defensive patterns

Strategy: validation

Validate before calling

fn weekly_schedule_reachable(rrule: &str) -> Result<bool, anyhow::Error> {
    if let AutomationSchedule::Weekly { byday, byhour, byminute } =
        AutomationSchedule::parse_rrule(rrule)?
    {
        if byday.is_empty() {
            return Ok(false);
        }
        let any_monday = chrono::NaiveDate::from_ymd_opt(2025, 1, 6).unwrap()
            .and_hms_opt(byhour, byminute, 0).unwrap();
        if chrono::Local.from_local_datetime(&any_monday).earliest().is_none() {
            return Ok(false); // wall time falls in a DST gap
        }
    }
    Ok(true)
}

Try / catch

match manager.update_automation(id, req) {
    Ok(record) => { /* ... */ }
    Err(e) if e.to_string().contains("next WEEKLY run") => {
        tracing::warn!(%e, "unreachable WEEKLY automation {id}; check BYDAY/time");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Constructing AutomationSchedule::Weekly directly with byday: vec![] instead of going through parse_rrule; or a timezone where resolve_local_datetime (from_local_datetime(...).earliest()) returns None for the BYHOUR/BYMINUTE on every listed weekday inside the 15-day window (FREQ=WEEKLY;BYDAY=SU;BYHOUR=2;BYMINUTE=30 only skirts this on the single spring-forward Sunday, since the following Sunday resolves).

Common situations: Programmatic construction of the schedule enum rather than parse_rrule; unit tests building Weekly literals with no days; sandboxed environments with exotic timezone data.

Related errors


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