{"record":{"id":"8d54907536a866f3","repo":"Hmbown/CodeWhale","slug":"weekly-schedules-require-byday","errorCode":null,"errorMessage":"WEEKLY schedules require BYDAY","messagePattern":"WEEKLY schedules require BYDAY","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/automation_manager.rs","lineNumber":368,"sourceCode":"                }\n                Ok(Self::Hourly {\n                    interval_hours,\n                    byday,\n                    anchor_hour,\n                    anchor_minute,\n                })\n            }\n            AutomationFrequency::Weekly => {\n                for key in parts.keys() {\n                    if key != \"FREQ\" && key != \"BYDAY\" && key != \"BYHOUR\" && key != \"BYMINUTE\" {\n                        bail!(\n                            \"Unsupported RRULE field '{key}' for WEEKLY. Allowed: FREQ,BYDAY,BYHOUR,BYMINUTE\"\n                        );\n                    }\n                }\n                let byday_raw = parts\n                    .get(\"BYDAY\")\n                    .ok_or_else(|| anyhow::anyhow!(\"WEEKLY schedules require BYDAY\"))?;\n                let byday = parse_byday(&byday_raw.to_ascii_uppercase())?;\n                if byday.is_empty() {\n                    bail!(\"BYDAY cannot be empty for WEEKLY schedules\");\n                }\n                let byhour = parts\n                    .get(\"BYHOUR\")\n                    .ok_or_else(|| anyhow::anyhow!(\"WEEKLY schedules require BYHOUR\"))?\n                    .parse::<u32>()\n                    .context(\"Failed to parse BYHOUR\")?;\n                let byminute = parts\n                    .get(\"BYMINUTE\")\n                    .ok_or_else(|| anyhow::anyhow!(\"WEEKLY schedules require BYMINUTE\"))?\n                    .parse::<u32>()\n                    .context(\"Failed to parse BYMINUTE\")?;\n\n                if byhour > 23 {\n                    bail!(\"BYHOUR must be between 0 and 23\");\n                }","sourceCodeStart":350,"sourceCodeEnd":386,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/automation_manager.rs#L350-L386","documentation":"AutomationSchedule::parse_rrule validates RRULE strings for scheduled automations. FREQ=WEEKLY requires BYDAY (two-letter day codes like MO,WE) to say which weekdays fire; WEEKLY allows only the fields FREQ,BYDAY,BYHOUR,BYMINUTE, and a string without the BYDAY key is rejected with this error at parse time, before any schedule is stored.","triggerScenarios":"Creating or updating an automation whose rrule is e.g. 'FREQ=WEEKLY;BYHOUR=9;BYMINUTE=30' (BYDAY omitted), passed via the automation tool create/update action or any caller of AutomationSchedule::parse_rrule.","commonSituations":"Hand-written RRULEs adapted from HOURLY examples (which make BYDAY optional); model-generated schedules copying the weekly template incompletely; forgetting that weekly needs all three of BYDAY/BYHOUR/BYMINUTE.","solutions":["Add BYDAY with one or more day codes: FREQ=WEEKLY;BYDAY=MO,WE;BYHOUR=9;BYMINUTE=30","Use uppercase field names exactly as FREQ/BYDAY/BYHOUR/BYMINUTE; any other key is rejected with its own 'Unsupported RRULE field' error","Validate the RRULE with parse_rrule before persisting or sending it to the automation tool"],"exampleFix":"// before\nlet rrule = \"FREQ=WEEKLY;BYHOUR=9;BYMINUTE=30\";\n// after\nlet rrule = \"FREQ=WEEKLY;BYDAY=MO,WE;BYHOUR=9;BYMINUTE=30\";","handlingStrategy":"validation","validationCode":"let parts: std::collections::BTreeMap<&str, &str> = rrule\n    .split(';')\n    .filter_map(|kv| kv.split_once('='))\n    .collect();\nif parts.get(\"FREQ\").copied() == Some(\"WEEKLY\") && !parts.contains_key(\"BYDAY\") {\n    anyhow::bail!(\"add BYDAY, e.g. FREQ=WEEKLY;BYDAY=MO,WE;BYHOUR=9;BYMINUTE=30\");\n}","typeGuard":"fn is_valid_weekly_rrule(rrule: &str) -> bool {\n    let parts: std::collections::BTreeMap<String, String> = rrule\n        .split(';')\n        .filter_map(|kv| kv.split_once('='))\n        .map(|(k, v)| (k.to_ascii_uppercase(), v.to_string()))\n        .collect();\n    parts.get(\"FREQ\").map(String::as_str) == Some(\"WEEKLY\")\n        && [\"BYDAY\", \"BYHOUR\", \"BYMINUTE\"].iter().all(|k| parts.contains_key(*k))\n        && parts.keys().all(|k| matches!(k.as_str(), \"FREQ\" | \"BYDAY\" | \"BYHOUR\" | \"BYMINUTE\"))\n}","tryCatchPattern":null,"preventionTips":["Always emit the full weekly template FREQ=WEEKLY;BYDAY=...;BYHOUR=..;BYMINUTE=.. — all three BY* fields are mandatory","Validate RRULEs with AutomationSchedule::parse_rrule before persisting them","Keep field names uppercase; any other key is rejected as unsupported","When generating schedules in code or prompts, start from the documented example rather than hand-truncating it"],"tags":["automation","rrule","scheduling","validation"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}