{"record":{"id":"240b2b2475adc9c4","repo":"Hmbown/CodeWhale","slug":"cron-field-name-range-start-must-be-end","errorCode":null,"errorMessage":"CRON {field_name} range start must be <= end","messagePattern":"CRON (.+?) range start must be <= end","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/automation_manager.rs","lineNumber":751,"sourceCode":"                let step = step\n                    .trim()\n                    .parse::<u32>()\n                    .with_context(|| format!(\"Failed to parse CRON {field_name} step\"))?;\n                if step == 0 {\n                    bail!(\"CRON {field_name} step must be >= 1\");\n                }\n                (base.trim(), step)\n            } else {\n                (part, 1)\n            };\n\n            let range = if base == \"*\" {\n                (min, max)\n            } else if let Some((start, end)) = base.split_once('-') {\n                let start = parse_cron_atom(start.trim(), min, max, names, field_name)?;\n                let end = parse_cron_atom(end.trim(), min, max, names, field_name)?;\n                if start > end {\n                    bail!(\"CRON {field_name} range start must be <= end\");\n                }\n                (start, end)\n            } else {\n                let start = parse_cron_atom(base, min, max, names, field_name)?;\n                if part.contains('/') {\n                    (start, max)\n                } else {\n                    (start, start)\n                }\n            };\n\n            let mut current = range.0;\n            while current <= range.1 {\n                if !values.contains(&current) {\n                    values.push(current);\n                }\n                let Some(next) = current.checked_add(step) else {\n                    break;","sourceCodeStart":733,"sourceCodeEnd":769,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/automation_manager.rs#L733-L769","documentation":"Range atoms must satisfy start <= end after name/number resolution; cron-style wraparound ranges are not supported. Overnight hour ranges like 20-6 and day-of-week ranges like SAT-SUN (6-0) fail because names resolve to numbers before the comparison.","triggerScenarios":"EXPR='0 20-6 * * *' (overnight hours); EXPR='0 0 * * 6-0' (SAT-SUN wrap); any reversed numeric or name range in any field.","commonSituations":"Porting expressions from schedulers that accept wrapping ranges; overnight maintenance windows written naturally as evening-to-morning.","solutions":["Split wrapping ranges into two list items: '0 0-6,20-23 * * *'","For SAT-SUN write '0 0 * * 0,6' or '6-7' (7 normalizes to Sunday 0)","Keep all ranges ascending","Dry-run parse_rrule before activating"],"exampleFix":"// before (wrapping overnight range unsupported)\nrrule = \"FREQ=CRON;EXPR=0 20-6 * * *\"\n\n// after\nrrule = \"FREQ=CRON;EXPR=0 20-23,0-6 * * *\"","handlingStrategy":"validation","validationCode":"fn cron_expr_valid(expr: &str) -> bool {\n    AutomationSchedule::parse_rrule(&format!(\"FREQ=CRON;EXPR={expr}\")).is_ok()\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remember ranges never wrap; split overnight windows into two list items","Write weekend ranges as 0,6 or ascending 6-7 (7 becomes Sunday 0)","Dry-run the EXPR before activation","Prefer explicit day lists for two-day ranges"],"tags":["rust","cron","range","validation"],"backgroundTag":"cron-range-order-invalid","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}