{"record":{"id":"3ce1f8f3578f4f47","repo":"Hmbown/CodeWhale","slug":"once-schedule-has-no-future-run-after","errorCode":null,"errorMessage":"Once schedule has no future run after {}","messagePattern":"Once schedule has no future run after (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/tui/src/automation_manager.rs","lineNumber":425,"sourceCode":"        after: DateTime<Utc>,\n        anchor_reference: DateTime<Utc>,\n    ) -> Result<DateTime<Utc>> {\n        self.next_after_in_timezone(after, anchor_reference, &Local)\n    }\n\n    fn next_after_in_timezone<Tz: TimeZone>(\n        &self,\n        after: DateTime<Utc>,\n        anchor_reference: DateTime<Utc>,\n        timezone: &Tz,\n    ) -> Result<DateTime<Utc>> {\n        let local_after = after.with_timezone(timezone);\n        match self {\n            Self::Once { at } => {\n                if *at > after {\n                    Ok(*at)\n                } else {\n                    bail!(\n                        \"Once schedule has no future run after {}\",\n                        after.to_rfc3339()\n                    )\n                }\n            }\n            Self::Hourly {\n                interval_hours,\n                byday,\n                anchor_hour,\n                anchor_minute,\n            } => {\n                if anchor_hour.is_some() || anchor_minute.is_some() {\n                    let local_anchor_reference = anchor_reference.with_timezone(timezone);\n                    let hour = anchor_hour.unwrap_or(local_anchor_reference.hour());\n                    let minute = anchor_minute.unwrap_or(0);\n                    let anchor_naive = local_anchor_reference\n                        .date_naive()\n                        .and_hms_opt(hour, minute, 0)","sourceCodeStart":407,"sourceCodeEnd":443,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/automation_manager.rs#L407-L443","documentation":"next_after_in_timezone for a Once { at } schedule can only ever return the single stored instant. When asked for the next run strictly after a moment that is at or past `at`, there is nothing left to fire, so it bails with the cutoff timestamp. This is the natural end-of-life of a one-shot schedule, surfaced as an error rather than a silent skip.","triggerScenarios":"Calling next_after/next_after_in_timezone on an ONCE schedule after its AT time has passed — e.g. the scheduler wakes late (machine asleep, process suspended) and recomputes the next run, or a missed-run recovery path asks what comes next.","commonSituations":"Laptop asleep at fire time; long queue or downtime; tests iterating next-run with an `after` beyond AT; UI previewing the 'next occurrence' of an expired one-shot.","solutions":["Treat this error as schedule completion: mark the automation done/disable it instead of retrying.","For missed one-shots you still want, recreate the rule with a future AT (`FREQ=ONCE;AT=...`) or switch to CRON.","When previewing, check `if let AutomationSchedule::Once { at } = sched { if *at <= after { /* expired */ } }` before calling."],"exampleFix":"// before\nlet next = sched.next_after(after, &tz)?; // bails: Once schedule has no future run\n\n// after\nlet next = match sched.next_after(after, &tz) {\n    Ok(t) => t,\n    Err(e) if e.to_string().contains(\"no future run\") => {\n        mark_automation_complete(&id); // one-shot fired/expired\n        return Ok(());\n    }\n    Err(e) => return Err(e),\n};","handlingStrategy":"fallback","validationCode":"if let AutomationSchedule::Once { at } = &schedule {\n    if *at <= after {\n        // One-shot already fired or expired; nothing to schedule.\n        disable_automation(&id)?;\n        return Ok(None);\n    }\n}\nlet next = schedule.next_after(after, &tz)?;","typeGuard":"fn is_expired_once(sched: &AutomationSchedule, after: chrono::DateTime<chrono::Utc>) -> bool {\n    matches!(sched, AutomationSchedule::Once { at } if *at <= after)\n}","tryCatchPattern":"match schedule.next_after(after, &tz) {\n    Ok(next) => Some(next),\n    Err(e) if e.to_string().contains(\"no future run\") => { mark_automation_complete(&id); None }\n    Err(e) => return Err(e),\n}","preventionTips":["Treat expired ONCE schedules as completion, not as an error to retry.","Disable or clean up one-shot automations after they fire.","Use CRON or recurring FREQ variants when late/missed firing matters."],"tags":["rrule","automation","schedule","once","expired"],"backgroundTag":"schedule-expired","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}