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
- Not actionable on the caller side; treat it as an internal invariant failure and report it upstream with the schedule and system clock details
- Recomputing from a fresh clock reading on the next scheduler tick usually clears it
- 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
- Normalize clock inputs by truncating sub-second precision before scheduling
- If you feed persisted timestamps into the scheduler, store already-truncated values
- Report recurrences upstream — this branch is effectively an invariant violation
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
- CRON schedule exceeded its range
- Unable to construct HOURLY anchor
- HOURLY schedule exceeded its range
- CRON schedules require EXPR
- Invalid CRON {field_name} value '{raw}'
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/c217538dcdf4fe7d.
Report an issue: GitHub.