Hmbown/CodeWhale · error · anyhow::Error

Unable to compute next anchored HOURLY run

Error message

Unable to compute next anchored HOURLY run

What it means

For HOURLY schedules with a BYHOUR/BYMINUTE anchor, next_after_in_timezone computes candidates as anchor + steps*INTERVAL and tries up to MAX_HOURLY_SEARCH_STEPS steps, keeping only candidates that match the optional BYDAY filter and resolve to a real local time (DST-safe). If the bounded search never yields a resolvable candidate strictly after the requested instant — e.g. every candidate in the window falls in a nonexistent local time or misses the BYDAY filter — it gives up with this error instead of looping unbounded.

Source

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

                            .ok_or_else(|| anyhow::anyhow!("HOURLY schedule exceeded its range"))?;

                        if byday
                            .as_ref()
                            .is_none_or(|days| days.contains(&candidate_naive.weekday()))
                            && let Some(candidate) =
                                resolve_local_datetime(timezone, candidate_naive)
                        {
                            let candidate = candidate.with_timezone(&Utc);
                            if candidate > after {
                                return Ok(candidate);
                            }
                        }

                        steps = steps
                            .checked_add(1)
                            .ok_or_else(|| anyhow::anyhow!("HOURLY schedule exceeded its range"))?;
                    }
                    bail!("Unable to compute next anchored HOURLY run");
                }

                let after_second = local_after.second();
                let after_nanosecond = local_after.nanosecond();
                let mut candidate = local_after + Duration::hours(i64::from(*interval_hours))
                    - Duration::seconds(i64::from(after_second))
                    - Duration::nanoseconds(i64::from(after_nanosecond));

                if let Some(days) = byday {
                    for _ in 0..(24 * 21) {
                        if days.contains(&candidate.weekday()) {
                            return Ok(candidate.with_timezone(&Utc));
                        }
                        candidate += Duration::hours(i64::from(*interval_hours));
                    }
                    bail!("Unable to compute next HOURLY run for BYDAY filter");
                }

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Switch to `FREQ=CRON;EXPR=...` for day-filtered daily/long-interval patterns — it expresses them directly.
  2. Reduce INTERVAL or drop BYDAY so candidates occur densely enough to land inside the search window.
  3. Pass an `after` close to the present; schedule previews far in the future can exhaust the step budget by themselves.

Example fix

// before
let s = AutomationSchedule::parse_rrule("FREQ=HOURLY;INTERVAL=24;BYDAY=MO,FR;BYHOUR=8;BYMINUTE=30")?;
let next = s.next_after(after, &tz)?; // may bail: Unable to compute next anchored HOURLY run

// after
let s = AutomationSchedule::parse_rrule("FREQ=CRON;EXPR=30 8 * * 1,5")?;
let next = s.next_after(after, &tz)?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Prefer CRON for day-filtered long-interval patterns before hitting search limits.
if interval_hours >= 24 && byday.is_some() {
    anyhow::bail!("use FREQ=CRON;EXPR='M H * * DAYS' instead of HOURLY with INTERVAL>=24 + BYDAY");
}

Type guard

fn anchored_hourly_searchable(interval_hours: u32, byday_filter: Option<&[chrono::Weekday]>) -> bool {
    byday_filter.is_none() || interval_hours <= 12 // dense enough to land inside MAX_HOURLY_SEARCH_STEPS
}

Try / catch

match schedule.next_after(after, &tz) {
    Ok(next) => next,
    Err(e) if e.to_string().contains("Unable to compute next anchored HOURLY run") => {
        AutomationSchedule::parse_rrule("FREQ=CRON;EXPR=30 8 * * 1,5")?.next_after(after, &tz)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A large INTERVAL (e.g. INTERVAL=24 or more) with a BYDAY filter whose weekdays never align within MAX_HOURLY_SEARCH_STEPS candidate steps; or anchored candidates repeatedly landing in a skipped local time around a DST transition; or querying next_after with an `after` extremely far from the anchor so the window of steps is spent catching up.

Common situations: Emulating DAILY with FREQ=HOURLY;INTERVAL=24;BYDAY=MO,FR plus an anchor; timezone databases with unusual transitions; passing a projected/inserted `after` far in the future when previewing schedules.

Related errors


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