{"record":{"id":"27acee85a982526e","repo":"Hmbown/CodeWhale","slug":"unsupported-rrule-field-key-for-hourly-allowe","errorCode":null,"errorMessage":"Unsupported RRULE field '{key}' for HOURLY. Allowed: FREQ,INTERVAL,BYDAY,BYHOUR,BYMINUTE","messagePattern":"Unsupported RRULE field '(.+?)' for HOURLY\\. Allowed: FREQ,INTERVAL,BYDAY,BYHOUR,BYMINUTE","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/automation_manager.rs","lineNumber":322,"sourceCode":"            Some(\"HOURLY\") => AutomationFrequency::Hourly,\n            Some(\"WEEKLY\") => AutomationFrequency::Weekly,\n            Some(\"CRON\") => return parse_cron_schedule(&parts),\n            Some(other) => {\n                bail!(\"Unsupported RRULE FREQ '{other}'. Supported: ONCE, HOURLY, WEEKLY, and CRON\")\n            }\n            None => bail!(\"RRULE must include FREQ\"),\n        };\n\n        match freq {\n            AutomationFrequency::Hourly => {\n                for key in parts.keys() {\n                    if key != \"FREQ\"\n                        && key != \"INTERVAL\"\n                        && key != \"BYDAY\"\n                        && key != \"BYHOUR\"\n                        && key != \"BYMINUTE\"\n                    {\n                        bail!(\n                            \"Unsupported RRULE field '{key}' for HOURLY. Allowed: FREQ,INTERVAL,BYDAY,BYHOUR,BYMINUTE\"\n                        );\n                    }\n                }\n                let interval_hours = parts\n                    .get(\"INTERVAL\")\n                    .map(|v| v.parse::<u32>())\n                    .transpose()\n                    .context(\"Failed to parse INTERVAL\")?\n                    .unwrap_or(1);\n                if interval_hours == 0 {\n                    bail!(\"INTERVAL must be >= 1 for HOURLY schedules\");\n                }\n                let byday = parts\n                    .get(\"BYDAY\")\n                    .map(|value| parse_byday(&value.to_ascii_uppercase()))\n                    .transpose()?;\n                let anchor_hour = parts","sourceCodeStart":304,"sourceCodeEnd":340,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/automation_manager.rs#L304-L340","documentation":"For FREQ=HOURLY the parser whitelists exactly FREQ, INTERVAL, BYDAY, BYHOUR, BYMINUTE. Any other key in the map — UNTIL, COUNT, BYMONTH, DTSTART, or even fields legal for other variants like CRON's EXPR — is rejected so unsupported semantics cannot be silently dropped.","triggerScenarios":"parse_rrule(\"FREQ=HOURLY;INTERVAL=2;UNTIL=20261231T000000Z\") or `FREQ=HOURLY;COUNT=5`, or mixing variants like `FREQ=HOURLY;EXPR=*/5 * * * *`.","commonSituations":"Copying a full RFC 5545 rule that carries UNTIL/COUNT/BYMONTH; incrementally adding fields to a working HOURLY rule without checking the whitelist.","solutions":["Strip the unsupported key(s); for a bounded run, enforce the end condition in your own scheduler loop, not in the RRULE.","Replace complex rules with `FREQ=CRON;EXPR=...` when cron can express them.","Keep HOURLY rules to the five allowed keys: FREQ,INTERVAL,BYDAY,BYHOUR,BYMINUTE."],"exampleFix":"// before\nlet s = AutomationSchedule::parse_rrule(\"FREQ=HOURLY;INTERVAL=2;UNTIL=20261231T000000Z\")?; // bails on UNTIL\n\n// after\nlet s = AutomationSchedule::parse_rrule(\"FREQ=HOURLY;INTERVAL=2\")?; // enforce UNTIL in your loop","handlingStrategy":"validation","validationCode":"const HOURLY_ALLOWED: &[&str] = &[\"FREQ\", \"INTERVAL\", \"BYDAY\", \"BYHOUR\", \"BYMINUTE\"];\nfor key in rrule.split(';').filter_map(|p| p.trim().split_once('=')) {\n    let k = key.0.trim().to_ascii_uppercase();\n    if !HOURLY_ALLOWED.contains(&k.as_str()) {\n        anyhow::bail!(\"{k} is not allowed for FREQ=HOURLY\");\n    }\n}","typeGuard":"fn hourly_keys_allowed(rrule: &str) -> bool {\n    rrule.split(';').filter_map(|p| p.trim().split_once('=')).all(|(k, _)| {\n        [\"FREQ\", \"INTERVAL\", \"BYDAY\", \"BYHOUR\", \"BYMINUTE\"].contains(&k.trim().to_ascii_uppercase().as_str())\n    })\n}","tryCatchPattern":"match AutomationSchedule::parse_rrule(&rrule) {\n    Ok(s) => s,\n    Err(e) if e.to_string().contains(\"for HOURLY\") => { /* strip unsupported key or use CRON */ return Err(e) }\n    Err(e) => return Err(e),\n}","preventionTips":["Model each schedule variant as its own typed struct and serialize only whitelisted keys.","Enforce bounds (UNTIL/COUNT) in your scheduler driver, not in the RRULE string.","Prefer FREQ=CRON for patterns the HOURLY grammar cannot express."],"tags":["rrule","automation","schedule","unsupported-field"],"backgroundTag":"rrule-unsupported-field","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}