Hmbown/CodeWhale · error · anyhow::Error

CRON {field_name} step must be >= 1

Error message

CRON {field_name} step must be >= 1

What it means

A step value of 0 in a cron field ('base/step') is rejected; steps must parse as u32 and be >= 1. '*/0', '5-10/0', and name-based bases with '/0' all fail here. A non-numeric step produces the sibling 'Failed to parse CRON {field} step' error instead.

Source

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

    fn parse(raw: &str, min: u32, max: u32, names: CronNameMap, field_name: &str) -> Result<Self> {
        let trimmed = raw.trim();
        if trimmed.is_empty() {
            bail!("CRON {field_name} field must not be empty");
        }
        let mut values = Vec::new();
        let is_wildcard = trimmed == "*";
        for part in trimmed.split(',') {
            let part = part.trim();
            if part.is_empty() {
                bail!("CRON {field_name} field contains an empty list item");
            }
            let (base, step) = if let Some((base, step)) = part.split_once('/') {
                let step = step
                    .trim()
                    .parse::<u32>()
                    .with_context(|| format!("Failed to parse CRON {field_name} step"))?;
                if step == 0 {
                    bail!("CRON {field_name} step must be >= 1");
                }
                (base.trim(), step)
            } else {
                (part, 1)
            };

            let range = if base == "*" {
                (min, max)
            } else if let Some((start, end)) = base.split_once('-') {
                let start = parse_cron_atom(start.trim(), min, max, names, field_name)?;
                let end = parse_cron_atom(end.trim(), min, max, names, field_name)?;
                if start > end {
                    bail!("CRON {field_name} range start must be <= end");
                }
                (start, end)
            } else {
                let start = parse_cron_atom(base, min, max, names, field_name)?;
                if part.contains('/') {

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Use a step >= 1: */5, */15, 2-30/10
  2. Guard computed steps against 0 before formatting the EXPR
  3. For 'as often as possible' use plain '*' (implicit step 1)
  4. Dry-run parse_rrule on the final expression

Example fix

// before
rrule = "FREQ=CRON;EXPR=*/0 * * * *"

// after
rrule = "FREQ=CRON;EXPR=*/5 * * * *"
Defensive patterns

Strategy: validation

Validate before calling

fn cron_steps_positive(expr: &str) -> bool {
    expr.split_whitespace().all(|field| {
        field.split(',').all(|item| match item.split_once('/') {
            Some((_, step)) => step.trim().parse::<u32>().is_ok_and(|s| s >= 1),
            None => true,
        })
    })
}

Prevention

When it happens

Trigger: EXPR='*/0 * * * *' (every zero minutes); EXPR='0 0 */0 * *'; templated steps where a computed divisor evaluated to 0.

Common situations: Templated cron generators computing a step from user-supplied numbers; placeholder '*/N' strings with N never substituted.

Related errors


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