Hmbown/CodeWhale · error · anyhow::Error

RRULE must include FREQ

Error message

RRULE must include FREQ

What it means

After collecting KEY=VALUE segments, parse_rrule looks up the `FREQ` key (case-insensitive) and finds none. FREQ is the mandatory first decision (which schedule variant to build), so an RRULE without it cannot be interpreted at all. Empty segments were already skipped, so a string of only separators or whitespace also lands here.

Source

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

            let Some((k, v)) = item.split_once('=') else {
                bail!("Invalid RRULE segment '{item}'");
            };
            parts.insert(k.trim().to_ascii_uppercase(), v.trim().to_string());
        }

        let freq = match parts
            .get("FREQ")
            .map(|value| value.trim().to_ascii_uppercase())
            .as_deref()
        {
            Some("ONCE") => return parse_once_schedule(&parts),
            Some("HOURLY") => AutomationFrequency::Hourly,
            Some("WEEKLY") => AutomationFrequency::Weekly,
            Some("CRON") => return parse_cron_schedule(&parts),
            Some(other) => {
                bail!("Unsupported RRULE FREQ '{other}'. Supported: ONCE, HOURLY, WEEKLY, and CRON")
            }
            None => bail!("RRULE must include FREQ"),
        };

        match freq {
            AutomationFrequency::Hourly => {
                for key in parts.keys() {
                    if key != "FREQ"
                        && key != "INTERVAL"
                        && key != "BYDAY"
                        && key != "BYHOUR"
                        && key != "BYMINUTE"
                    {
                        bail!(
                            "Unsupported RRULE field '{key}' for HOURLY. Allowed: FREQ,INTERVAL,BYDAY,BYHOUR,BYMINUTE"
                        );
                    }
                }
                let interval_hours = parts
                    .get("INTERVAL")

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Start every rule with a FREQ part: `FREQ=ONCE|HOURLY|WEEKLY|CRON;...`.
  2. Validate presence before parsing: `rrule.split(';').any(|p| p.trim().to_ascii_uppercase().starts_with("FREQ="))`.
  3. If the rule is user-supplied, reject it at the UI boundary with the supported-grammar hint.

Example fix

// before
let sched = AutomationSchedule::parse_rrule("BYDAY=MO;BYHOUR=9")?; // bails: RRULE must include FREQ

// after
let sched = AutomationSchedule::parse_rrule("FREQ=WEEKLY;BYDAY=MO;BYHOUR=9;BYMINUTE=0")?;
Defensive patterns

Strategy: validation

Validate before calling

if !rrule
    .split(';')
    .any(|p| p.trim().to_ascii_uppercase().starts_with("FREQ="))
{
    anyhow::bail!("RRULE must start with FREQ=ONCE|HOURLY|WEEKLY|CRON");
}

Type guard

fn has_freq_part(rrule: &str) -> bool {
    rrule.split(';').any(|p| p.trim().to_ascii_uppercase().starts_with("FREQ="))
}

Try / catch

match AutomationSchedule::parse_rrule(&rrule) {
    Ok(s) => s,
    Err(e) if e.to_string().contains("must include FREQ") => { /* prepend FREQ=... or re-prompt */ return Err(e) }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling parse_rrule with `"BYDAY=MO;BYHOUR=9"`, `""`, `";;;"`, or a rule where FREQ is misspelled (`FREQ=` empty also yields no usable value once consumed elsewhere — the key must exist).

Common situations: Building RRULE strings programmatically and forgetting to append the FREQ part; user config with only the day/time fields; strings that got truncated before FREQ.

Related errors


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