Hmbown/CodeWhale · error · anyhow::Error

CRON field contains an empty list item

Error message

CRON {field_name} field contains an empty list item

What it means

Within a comma-separated cron field, empty list items are invalid: each part between commas must contain a value. The parser splits on ',' and bails when any resulting part trims to empty, e.g. '1,,5' or a trailing comma '1,5,'.

Solutions

  1. Remove the extra/stray comma so every item has a value, e.g. '1,5' instead of '1,,5'
  2. Build the field by filtering empty entries before joining: parts.iter().filter(|p| !p.is_empty()).join(",")
  3. Trim the whole field and strip trailing commas before saving

Example fix

// before
let field = format!("{},,{}", a, b); // empty item
// after
let field = [a, b].iter().filter(|v| !v.is_empty()).cloned().collect::<Vec<_>>().join(",");
Defensive patterns

Strategy: validation

Validate before calling

fn no_empty_list_items(field: &str) -> bool {
    field.split(',').all(|p| !p.trim().is_empty())
}

Prevention

When it happens

Trigger: A cron field like 'minutes=1,,5', '*/5, ' or '0,15,30,' — any double comma, leading, or trailing comma in minute/hour/day/month/weekday.

Common situations: Programmatic list building joined with commas where a list element was empty (e.g. vec of Option values stringified); hand-editing a schedule and deleting a value but not its comma.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/b0f206273bd0e04a. Report an issue: GitHub.

Appendix: source

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

#[derive(Debug, Clone)]
struct CronField {
    values: Vec<u32>,
    is_wildcard: bool,
}

impl CronField {
    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)?;

View on GitHub (pinned to 73e0f67d83)