{"record":{"id":"d8533accfa5f9766","repo":"Hmbown/CodeWhale","slug":"unable-to-compute-next-hourly-run-for-byday-filter","errorCode":null,"errorMessage":"Unable to compute next HOURLY run for BYDAY filter","messagePattern":"Unable to compute next HOURLY run for BYDAY filter","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/automation_manager.rs","lineNumber":498,"sourceCode":"                            .ok_or_else(|| anyhow::anyhow!(\"HOURLY schedule exceeded its range\"))?;\n                    }\n                    bail!(\"Unable to compute next anchored HOURLY run\");\n                }\n\n                let after_second = local_after.second();\n                let after_nanosecond = local_after.nanosecond();\n                let mut candidate = local_after + Duration::hours(i64::from(*interval_hours))\n                    - Duration::seconds(i64::from(after_second))\n                    - Duration::nanoseconds(i64::from(after_nanosecond));\n\n                if let Some(days) = byday {\n                    for _ in 0..(24 * 21) {\n                        if days.contains(&candidate.weekday()) {\n                            return Ok(candidate.with_timezone(&Utc));\n                        }\n                        candidate += Duration::hours(i64::from(*interval_hours));\n                    }\n                    bail!(\"Unable to compute next HOURLY run for BYDAY filter\");\n                }\n\n                Ok(candidate.with_timezone(&Utc))\n            }\n            Self::Weekly {\n                byday,\n                byhour,\n                byminute,\n            } => {\n                for day_offset in 0..15 {\n                    let date = local_after.date_naive() + Duration::days(i64::from(day_offset));\n                    if !byday.contains(&date.weekday()) {\n                        continue;\n                    }\n                    let Some(candidate_naive) = date.and_hms_opt(*byhour, *byminute, 0) else {\n                        continue;\n                    };\n                    if let Some(candidate) = resolve_local_datetime(timezone, candidate_naive)","sourceCodeStart":480,"sourceCodeEnd":516,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/automation_manager.rs#L480-L516","documentation":"Thrown by the unanchored HOURLY branch of AutomationSchedule::next_after_in_timezone when a BYDAY filter cannot be satisfied. The code advances a candidate time by INTERVAL hours for up to 24*21 = 504 steps and bails if candidate.weekday() never appears in the BYDAY list. Because each step advances by INTERVAL hours, an INTERVAL that is a multiple of 168 (a whole number of weeks, e.g. 168 or 336) freezes the weekday, so a BYDAY list excluding that weekday can never match.","triggerScenarios":"Computing next_run_at for an Active automation (create_automation/update_automation with status Active) whose rrule is FREQ=HOURLY;INTERVAL=168;BYDAY=MO when the first candidate after 'now' is not a Monday: weekday never changes across 504 steps and next_after_with_anchor returns this error.","commonSituations":"Encoding a weekly intent as FREQ=HOURLY with INTERVAL=168 (assuming INTERVAL counts days); INTERVAL=24*N where N is a multiple of 7 combined with BYDAY; rrules copied from tools that accept multi-week hourly intervals.","solutions":["Use FREQ=WEEKLY;BYDAY=MO;BYHOUR=H;BYMINUTE=M for once-per-week wall-clock schedules","If HOURLY semantics are required, choose an INTERVAL that is not a multiple of 168 (e.g. 24), or include the frozen weekday in BYDAY","Dry-run AutomationSchedule::parse_rrule plus a next-run computation before flipping an automation to Active","Catch the error at the manager boundary and pause the automation instead of aborting the scheduling loop"],"exampleFix":"// before (weekday frozen, can never match)\nrrule = \"FREQ=HOURLY;INTERVAL=168;BYDAY=MO\"\n\n// after (weekly wall-clock schedule)\nrrule = \"FREQ=WEEKLY;BYDAY=MO;BYHOUR=9;BYMINUTE=0\"","handlingStrategy":"validation","validationCode":"fn hourly_byday_reachable(rrule: &str) -> Result<bool, anyhow::Error> {\n    if let AutomationSchedule::Hourly { interval_hours, byday: Some(days), .. } =\n        AutomationSchedule::parse_rrule(rrule)?\n    {\n        if interval_hours > 0 && interval_hours % 168 == 0 {\n            // Weekday is frozen at the first candidate's weekday.\n            let first = chrono::Local::now()\n                + chrono::Duration::hours(i64::from(interval_hours));\n            return Ok(days.contains(&first.weekday()));\n        }\n    }\n    Ok(true)\n}","typeGuard":null,"tryCatchPattern":"match manager.update_automation(id, req) {\n    Ok(record) => { /* persisted with next_run_at */ }\n    Err(e) if e.to_string().contains(\"HOURLY run for BYDAY filter\") => {\n        // Schedule can never fire: keep it paused and surface a config error.\n        tracing::warn!(%e, \"unreachable HOURLY/BYDAY automation {id}\");\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Never encode weekly cadence as INTERVAL=168 under FREQ=HOURLY; use FREQ=WEEKLY","Pre-validate rrule strings by parsing and computing the next run before setting status Active","Treat any interval that is a whole number of weeks combined with BYDAY as a config smell","Log automation id and rrule whenever next-run computation fails so bad rules are findable"],"tags":["rust","rrule","schedule","byday","hourly","automation"],"backgroundTag":"recurrence-next-occurrence-not-found","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}