{"record":{"id":"5873b4fa55dc3b2d","repo":"nikivdev/code","slug":"failed-to-resolve-local-midnight","errorCode":null,"errorMessage":"failed to resolve local midnight","messagePattern":"failed to resolve local midnight","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/ssh_keys.rs","lineNumber":526,"sourceCode":"\nfn unlock_expires_at(entry: &SshKeyUnlock) -> Option<DateTime<Utc>> {\n    DateTime::<Utc>::from_timestamp(entry.expires_at, 0)\n}\n\nfn next_local_midnight_utc() -> Result<DateTime<Utc>> {\n    let now = Local::now();\n    let tomorrow = now\n        .date_naive()\n        .succ_opt()\n        .ok_or_else(|| anyhow::anyhow!(\"failed to calculate next day\"))?;\n    let naive = tomorrow\n        .and_hms_opt(0, 0, 0)\n        .ok_or_else(|| anyhow::anyhow!(\"failed to build midnight time\"))?;\n    let local_dt = Local\n        .from_local_datetime(&naive)\n        .single()\n        .or_else(|| Local.from_local_datetime(&naive).earliest())\n        .ok_or_else(|| anyhow::anyhow!(\"failed to resolve local midnight\"))?;\n    Ok(local_dt.with_timezone(&Utc))\n}\n\nfn prompt_touch_id() -> Result<()> {\n    if !cfg!(target_os = \"macos\") {\n        bail!(\"Touch ID is not available on this OS\");\n    }\n    if std::env::var(\"FLOW_NO_TOUCH_ID\").is_ok() || !std::io::stdin().is_terminal() {\n        bail!(\"Touch ID prompt requires an interactive terminal\");\n    }\n\n    let reason = \"Flow needs Touch ID to unlock SSH keys.\";\n    let reason = reason.replace('\\\\', \"\\\\\\\\\").replace('\"', \"\\\\\\\"\");\n    let script = format!(\n        r#\"ObjC.import('stdlib');\nObjC.import('Foundation');\nObjC.import('LocalAuthentication');\nconst context = $.LAContext.alloc.init;","sourceCodeStart":508,"sourceCodeEnd":544,"githubUrl":"https://github.com/nikivdev/code/blob/a747e741ae92c09071d0ae946ab48488adcff1ce/src/ssh_keys.rs#L508-L544","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n.ok_or_else(|| anyhow::anyhow!(\"failed to resolve local midnight\"))?;\n// after\n.single()\n.or_else(|| Local.from_local_datetime(&naive).earliest())\n.or_else(|| Local.from_local_datetime(&naive).latest())\n.ok_or_else(|| anyhow::anyhow!(\"failed to resolve local midnight\"))?;","handlingStrategy":"fallback","validationCode":"use chrono::TimeZone;\nlet naive = chrono::Local::now().date_naive().succ_opt()?.and_hms_opt(0, 0, 0)?;\nlet res = chrono::Local.from_local_datetime(&naive);\nlet ok = matches!(res, chrono::LocalResult::Single(_) | chrono::LocalResult::Ambiguous(_, _));\nif !ok { eprintln!(\"local midnight does not exist tomorrow (DST gap); expiry will fall back\"); }","typeGuard":null,"tryCatchPattern":"match unlock_ssh_key() {\n    Ok(expiry) => expiry,\n    Err(e) if e.to_string().contains(\"failed to resolve local midnight\") => {\n        Utc::now() + chrono::Duration::hours(24) // fallback expiry\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["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"],"tags":["chrono","datetime","dst","timezone"],"backgroundTag":"dst-ambiguous-time","analyzedSha":"a747e741ae92c09071d0ae946ab48488adcff1ce","analyzedAt":"2026-09-01T22:43:55.719Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}