nikivdev/code · error
failed to resolve local midnight
Error message
failed to resolve local midnight
What it means
next_local_midnight_utc converts the naive local midnight into a concrete local DateTime. When a DST transition makes midnight ambiguous or skipped, from_local_datetime().single() and .earliest() can both return None, and the function then throws 'failed to resolve local midnight' because no valid local instant corresponds to tomorrow at 00:00.
Source
Thrown at src/ssh_keys.rs:526
fn unlock_expires_at(entry: &SshKeyUnlock) -> Option<DateTime<Utc>> {
DateTime::<Utc>::from_timestamp(entry.expires_at, 0)
}
fn next_local_midnight_utc() -> Result<DateTime<Utc>> {
let now = Local::now();
let tomorrow = now
.date_naive()
.succ_opt()
.ok_or_else(|| anyhow::anyhow!("failed to calculate next day"))?;
let naive = tomorrow
.and_hms_opt(0, 0, 0)
.ok_or_else(|| anyhow::anyhow!("failed to build midnight time"))?;
let local_dt = Local
.from_local_datetime(&naive)
.single()
.or_else(|| Local.from_local_datetime(&naive).earliest())
.ok_or_else(|| anyhow::anyhow!("failed to resolve local midnight"))?;
Ok(local_dt.with_timezone(&Utc))
}
fn prompt_touch_id() -> Result<()> {
if !cfg!(target_os = "macos") {
bail!("Touch ID is not available on this OS");
}
if std::env::var("FLOW_NO_TOUCH_ID").is_ok() || !std::io::stdin().is_terminal() {
bail!("Touch ID prompt requires an interactive terminal");
}
let reason = "Flow needs Touch ID to unlock SSH keys.";
let reason = reason.replace('\\', "\\\\").replace('"', "\\\"");
let script = format!(
r#"ObjC.import('stdlib');
ObjC.import('Foundation');
ObjC.import('LocalAuthentication');
const context = $.LAContext.alloc.init;View on GitHub (pinned to a747e741ae)
Solutions
- Set the system timezone to one without midnight DST transitions, or update the tz database
- In code, fall back to the latest() resolution or noon instead of midnight when single()/earliest() are None
- Use Local.with_ymd_and_hms and check LocalResult variants explicitly, handling None with a +1h offset
Example fix
// before
.ok_or_else(|| anyhow::anyhow!("failed to resolve local midnight"))?;
// after
.single()
.or_else(|| Local.from_local_datetime(&naive).earliest())
.or_else(|| Local.from_local_datetime(&naive).latest())
.ok_or_else(|| anyhow::anyhow!("failed to resolve local midnight"))?; Defensive patterns
Strategy: fallback
Validate before calling
use chrono::TimeZone;
let naive = chrono::Local::now().date_naive().succ_opt()?.and_hms_opt(0, 0, 0)?;
let res = chrono::Local.from_local_datetime(&naive);
let ok = matches!(res, chrono::LocalResult::Single(_) | chrono::LocalResult::Ambiguous(_, _));
if !ok { eprintln!("local midnight does not exist tomorrow (DST gap); expiry will fall back"); } Try / catch
match unlock_ssh_key() {
Ok(expiry) => expiry,
Err(e) if e.to_string().contains("failed to resolve local midnight") => {
Utc::now() + chrono::Duration::hours(24) // fallback expiry
}
Err(e) => return Err(e),
} Prevention
- Handle chrono::LocalResult::None explicitly instead of unwrapping
- Prefer .earliest()/.latest() chain fallbacks for DST-gap times
- Test date logic in timezones with midnight DST transitions
When it happens
Trigger: Calling next_local_midnight_utc (via unlock_ssh_key) when tomorrow at 00:00 local time falls inside a DST spring-forward gap in the machine's timezone (midnight does not exist that day), and even the earliest() resolution fails.
Common situations: Running on a machine whose timezone has DST transitions at midnight (e.g. some historical/observance timezones like America/Sao_Paulo before 2019, or Asia/Beirut), with the system clock near such a transition.
Related errors
AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01).
Data as JSON: /api/errors/5873b4fa55dc3b2d.
Report an issue: GitHub.