Hmbown/CodeWhale · warning

Unable to round CRON search start

Error message

Unable to round CRON search start

What it means

Before scanning for the next CRON match, the scheduler truncates the local start time to the minute via with_second(0).and_then(|dt| dt.with_nanosecond(0)). chrono's in-place setters return None only when the existing value is out of range — in practice a leap-second internal representation where the nanosecond field is >= 1_000_000_000 and cannot be truncated in place. A defensive, near-unreachable branch.

Source

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

                    }
                    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);
                        }
                    }
                    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");
            }

View on GitHub (pinned to 8880682c63)

Solutions

  1. Not actionable on the caller side; treat it as an internal invariant failure and report it upstream with the schedule and system clock details
  2. Recomputing from a fresh clock reading on the next scheduler tick usually clears it
  3. If you control the clock source, feed truncated (non-leap-second) timestamps
Defensive patterns

Strategy: fallback

Validate before calling

let now_local = chrono::Local::now().naive_local();
if now_local.nanosecond() >= 1_000_000_000 {
    // leap-second representation: re-derive from truncated UTC
    now_local = chrono::Utc::now().trunc_subsecs(0).with_timezone(&chrono::Local).naive_local();
}

Try / catch

match schedule.next_run(after) {
    Ok(next) => Some(next),
    Err(err) if err.to_string() == "Unable to round CRON search start" => {
        log::warn!("leap-second clock value; retrying CRON scheduling next tick");
        None // retry on the next scheduler tick with a fresh clock reading
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: local_after carrying a leap-second-style NaiveDateTime (nanosecond component beyond normal range, as produced by some clock sources at a leap second) at the moment a CRON schedule computes its next run.

Common situations: Essentially none: requires an exotic system clock producing leap-second timestamps while a CRON automation is scheduled.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/c217538dcdf4fe7d. Report an issue: GitHub.