BloopAI/vibe-kanban · error

validated digest hour

Error message

validated digest hour

What it means

`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.

Source

Thrown at crates/remote/src/digest/task.rs:144

async fn acquire_run_lock(pool: &PgPool) -> Option<DigestRunLock> {
    match DigestRepository::try_acquire_run_lock(pool).await {
        Ok(Some(lock)) => Some(lock),
        Ok(None) => {
            info!("Skipping notification digest cycle because another instance is running it");
            None
        }
        Err(error) => {
            error!(error = %error, "Failed to acquire notification digest lock");
            None
        }
    }
}

fn next_run_at(now: DateTime<Utc>, run_hour_utc: u32) -> DateTime<Utc> {
    let today = now.date_naive();
    let today_run = today
        .and_hms_opt(run_hour_utc, 0, 0)
        .expect("validated digest hour");

    let next_naive = if now.hour() < run_hour_utc {
        today_run
    } else {
        today
            .checked_add_days(Days::new(1))
            .expect("date overflow for digest schedule")
            .and_hms_opt(run_hour_utc, 0, 0)
            .expect("validated digest hour")
    };

    DateTime::from_naive_utc_and_offset(next_naive, Utc)
}

View on GitHub (pinned to 4deb7eca8f)

Solutions

  1. Validate run_hour_utc <= 23 at config load time (e.g. clamp or return Err from config parsing) before it reaches next_run_at
  2. Replace expect with and_hms_opt(...).unwrap_or_else(|| now) or propagate an error via Result
  3. Add a unit test asserting next_run_at panics/errors only for hours > 23 and parses env with range validation

Example fix

// before
let today_run = today.and_hms_opt(run_hour_utc, 0, 0).expect("validated digest hour");
// after
let today_run = today.and_hms_opt(run_hour_utc.min(23), 0, 0)
    .ok_or_else(|| anyhow::anyhow!("DIGEST_RUN_HOUR_UTC must be 0-23, got {run_hour_utc}"))?;
Defensive patterns

Strategy: validation

Validate before calling

fn validate_run_hour(h: u32) -> Result<u32, String> {
    if h <= 23 { Ok(h) } else { Err(format!("run_hour_utc must be 0-23, got {h}")) }
}

Type guard

fn is_valid_hour(h: u32) -> bool { h <= 23 }

Try / catch

std::panic::catch_unwind(|| next_run_at(now, run_hour_utc)) — or better, convert to Result and use ?

Prevention

When it happens

Trigger: `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.

Common situations: 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.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of BloopAI/vibe-kanban@4deb7eca8f (2026-08-29). Data as JSON: /api/errors/760668a9587c5450. Report an issue: GitHub.