{"record":{"id":"760668a9587c5450","repo":"BloopAI/vibe-kanban","slug":"validated-digest-hour","errorCode":null,"errorMessage":"validated digest hour","messagePattern":"validated digest hour","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/remote/src/digest/task.rs","lineNumber":144,"sourceCode":"async fn acquire_run_lock(pool: &PgPool) -> Option<DigestRunLock> {\n    match DigestRepository::try_acquire_run_lock(pool).await {\n        Ok(Some(lock)) => Some(lock),\n        Ok(None) => {\n            info!(\"Skipping notification digest cycle because another instance is running it\");\n            None\n        }\n        Err(error) => {\n            error!(error = %error, \"Failed to acquire notification digest lock\");\n            None\n        }\n    }\n}\n\nfn next_run_at(now: DateTime<Utc>, run_hour_utc: u32) -> DateTime<Utc> {\n    let today = now.date_naive();\n    let today_run = today\n        .and_hms_opt(run_hour_utc, 0, 0)\n        .expect(\"validated digest hour\");\n\n    let next_naive = if now.hour() < run_hour_utc {\n        today_run\n    } else {\n        today\n            .checked_add_days(Days::new(1))\n            .expect(\"date overflow for digest schedule\")\n            .and_hms_opt(run_hour_utc, 0, 0)\n            .expect(\"validated digest hour\")\n    };\n\n    DateTime::from_naive_utc_and_offset(next_naive, Utc)\n}\n","sourceCodeStart":126,"sourceCodeEnd":158,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/crates/remote/src/digest/task.rs#L126-L158","documentation":"`and_hms_opt` returns an Option and yields None if the given hour/minute/second cannot form a valid NaiveTime (e.g. hour >= 24). The code unwraps it with expect(\"validated digest hour\"), panicking if `run_hour_utc` is not a valid UTC hour (0-23). The panic message is a developer assertion that the digest hour configuration is sane.","triggerScenarios":"`digest_loop` calls `next_run_at(now, run_hour_utc)` with a `run_hour_utc` value of 24 or more (or otherwise unrepresentable), so `today.and_hms_opt(run_hour_utc, 0, 0)` returns None and the expect panics.","commonSituations":"Misconfigured DIGEST_RUN_HOUR_UTC-style environment variable parsed without range checking; a user setting hour 25 or a negative value cast to u32; a code change introducing a bad default.","solutions":["Validate run_hour_utc <= 23 at config load time (e.g. clamp or return Err from config parsing) before it reaches next_run_at","Replace expect with and_hms_opt(...).unwrap_or_else(|| now) or propagate an error via Result","Add a unit test asserting next_run_at panics/errors only for hours > 23 and parses env with range validation"],"exampleFix":"// before\nlet today_run = today.and_hms_opt(run_hour_utc, 0, 0).expect(\"validated digest hour\");\n// after\nlet today_run = today.and_hms_opt(run_hour_utc.min(23), 0, 0)\n    .ok_or_else(|| anyhow::anyhow!(\"DIGEST_RUN_HOUR_UTC must be 0-23, got {run_hour_utc}\"))?;","handlingStrategy":"validation","validationCode":"fn validate_run_hour(h: u32) -> Result<u32, String> {\n    if h <= 23 { Ok(h) } else { Err(format!(\"run_hour_utc must be 0-23, got {h}\")) }\n}","typeGuard":"fn is_valid_hour(h: u32) -> bool { h <= 23 }","tryCatchPattern":"std::panic::catch_unwind(|| next_run_at(now, run_hour_utc)) — or better, convert to Result and use ?","preventionTips":["Validate the digest-hour env var at config load, not at scheduling time","Clamp or reject out-of-range hours with a clear error message","Add unit tests covering invalid hour values"],"tags":["rust","panic","chrono","configuration"],"backgroundTag":"invalid-config-value","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}