{"record":{"id":"93e2307880182a89","repo":"Hmbown/CodeWhale","slug":"unable-to-compute-next-anchored-hourly-run","errorCode":null,"errorMessage":"Unable to compute next anchored HOURLY run","messagePattern":"Unable to compute next anchored HOURLY run","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/automation_manager.rs","lineNumber":482,"sourceCode":"                            .ok_or_else(|| anyhow::anyhow!(\"HOURLY schedule exceeded its range\"))?;\n\n                        if byday\n                            .as_ref()\n                            .is_none_or(|days| days.contains(&candidate_naive.weekday()))\n                            && let Some(candidate) =\n                                resolve_local_datetime(timezone, candidate_naive)\n                        {\n                            let candidate = candidate.with_timezone(&Utc);\n                            if candidate > after {\n                                return Ok(candidate);\n                            }\n                        }\n\n                        steps = steps\n                            .checked_add(1)\n                            .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","sourceCodeStart":464,"sourceCodeEnd":500,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/automation_manager.rs#L464-L500","documentation":"For HOURLY schedules with a BYHOUR/BYMINUTE anchor, next_after_in_timezone computes candidates as anchor + steps*INTERVAL and tries up to MAX_HOURLY_SEARCH_STEPS steps, keeping only candidates that match the optional BYDAY filter and resolve to a real local time (DST-safe). If the bounded search never yields a resolvable candidate strictly after the requested instant — e.g. every candidate in the window falls in a nonexistent local time or misses the BYDAY filter — it gives up with this error instead of looping unbounded.","triggerScenarios":"A large INTERVAL (e.g. INTERVAL=24 or more) with a BYDAY filter whose weekdays never align within MAX_HOURLY_SEARCH_STEPS candidate steps; or anchored candidates repeatedly landing in a skipped local time around a DST transition; or querying next_after with an `after` extremely far from the anchor so the window of steps is spent catching up.","commonSituations":"Emulating DAILY with FREQ=HOURLY;INTERVAL=24;BYDAY=MO,FR plus an anchor; timezone databases with unusual transitions; passing a projected/inserted `after` far in the future when previewing schedules.","solutions":["Switch to `FREQ=CRON;EXPR=...` for day-filtered daily/long-interval patterns — it expresses them directly.","Reduce INTERVAL or drop BYDAY so candidates occur densely enough to land inside the search window.","Pass an `after` close to the present; schedule previews far in the future can exhaust the step budget by themselves."],"exampleFix":"// before\nlet s = AutomationSchedule::parse_rrule(\"FREQ=HOURLY;INTERVAL=24;BYDAY=MO,FR;BYHOUR=8;BYMINUTE=30\")?;\nlet next = s.next_after(after, &tz)?; // may bail: Unable to compute next anchored HOURLY run\n\n// after\nlet s = AutomationSchedule::parse_rrule(\"FREQ=CRON;EXPR=30 8 * * 1,5\")?;\nlet next = s.next_after(after, &tz)?;","handlingStrategy":"try-catch","validationCode":"// Prefer CRON for day-filtered long-interval patterns before hitting search limits.\nif interval_hours >= 24 && byday.is_some() {\n    anyhow::bail!(\"use FREQ=CRON;EXPR='M H * * DAYS' instead of HOURLY with INTERVAL>=24 + BYDAY\");\n}","typeGuard":"fn anchored_hourly_searchable(interval_hours: u32, byday_filter: Option<&[chrono::Weekday]>) -> bool {\n    byday_filter.is_none() || interval_hours <= 12 // dense enough to land inside MAX_HOURLY_SEARCH_STEPS\n}","tryCatchPattern":"match schedule.next_after(after, &tz) {\n    Ok(next) => next,\n    Err(e) if e.to_string().contains(\"Unable to compute next anchored HOURLY run\") => {\n        AutomationSchedule::parse_rrule(\"FREQ=CRON;EXPR=30 8 * * 1,5\")?.next_after(after, &tz)?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Express day-filtered daily cadences as FREQ=CRON, not FREQ=HOURLY;INTERVAL=24;BYDAY=... .","Keep HOURLY intervals small enough that matching weekdays occur within the bounded search window.","Query next-run with `after` near the present; far-future previews can exhaust the step budget."],"tags":["rrule","automation","schedule","hourly","dst"],"backgroundTag":"schedule-next-run-unresolvable","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}