{"record":{"id":"09f99b838b11a780","repo":"jdx/mise","slug":"elapsed-timestamp-delta-must-fit-into-u64","errorCode":null,"errorMessage":"elapsed timestamp delta must fit into u64","messagePattern":"elapsed timestamp delta must fit into u64","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/duration.rs","lineNumber":21,"sourceCode":"\nuse eyre::{Result, bail};\nuse jiff::{Span, Timestamp, Zoned, civil::date};\n\npub(crate) const HOURLY: Duration = Duration::from_secs(60 * 60);\npub(crate) const DAILY: Duration = Duration::from_secs(60 * 60 * 24);\npub(crate) const WEEKLY: Duration = Duration::from_secs(60 * 60 * 24 * 7);\n\n/// Returns the number of whole seconds from `from` to `to`, rounded up.\n///\n/// Returns 0 when `from >= to` so callers don't have to guard against the\n/// degenerate \"cutoff is already in the future\" case.\npub(crate) fn elapsed_seconds_ceil(from: Timestamp, to: Timestamp) -> u64 {\n    if from >= to {\n        return 0;\n    }\n    let nanos = to.as_nanosecond() - from.as_nanosecond();\n    u64::try_from((nanos + 999_999_999) / 1_000_000_000)\n        .expect(\"elapsed timestamp delta must fit into u64\")\n}\n\n/// Returns a stable \"now\" timestamp for the lifetime of the process.\n///\n/// This is used for resolving relative durations (e.g. `minimum_release_age = \"3d\"`)\n/// consistently: every resolution of the same relative duration within a single\n/// mise invocation produces the same absolute timestamp, and downstream code\n/// that converts the absolute timestamp back to a duration (e.g. for npm's\n/// `--min-release-age`) gets the exact duration the user specified rather than\n/// a slightly-larger value due to wall clock drift between phases.\npub(crate) fn process_now() -> Timestamp {\n    static PROCESS_NOW: OnceLock<Timestamp> = OnceLock::new();\n    *PROCESS_NOW.get_or_init(Timestamp::now)\n}\n\npub(crate) fn parse_duration(s: &str) -> Result<Duration> {\n    match s.parse::<Span>() {\n        Ok(span) => {","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/duration.rs#L3-L39","documentation":"mise panics with 'elapsed timestamp delta must fit into u64' in `elapsed_seconds_ceil` when the difference between two timestamps, rounded up to whole seconds, exceeds `u64::MAX` — i.e. roughly 584 billion years. The function computes nanosecond deltas between a reference `from` and `to` timestamp (used for `minimum_release_age` checks); since both come from a consistent process clock, this should be impossible and the expect marks it as an invariant.","triggerScenarios":"Calling `elapsed_seconds_ceil` with timestamps from inconsistent epochs or corrupted nanosecond values — e.g. a `Timestamp::as_nanosecond` implementation backed by a clock that can go negative or saturate, or callers like `build_transitive_release_age_args`/`aube_project_config` constructing `to` far in the future from a malformed duration.","commonSituations":"A misconfigured or absurd `minimum_release_age` duration (e.g. a huge relative value) resolved into a far-future timestamp; clock changes or fake/test clocks with negative or extreme values; a change of the timestamp representation widening/narrowing types.","solutions":["Verify both timestamps use the same epoch and are produced by `stable_now`/the intended clock","Clamp or validate the resolved release-age timestamp before computing the delta","Return a saturating value (`u64::MAX`) or an error instead of panicking if extreme durations are possible","Check for a typo'd duration unit in `minimum_release_age` config producing an enormous timestamp"],"exampleFix":"// before\nu64::try_from((nanos + 999_999_999) / 1_000_000_000)\n    .expect(\"elapsed timestamp delta must fit into u64\")\n// after\nu64::try_from((nanos + 999_999_999) / 1_000_000_000)\n    .unwrap_or(u64::MAX) // saturate instead of panicking","handlingStrategy":"validation","validationCode":"if to.as_nanosecond() - from.as_nanosecond() > u64::MAX as i128 { return Err(...); }","typeGuard":"fn fits_u64(n: i128) -> bool { (0..=u64::MAX as i128).contains(&n) }","tryCatchPattern":null,"preventionTips":["Source both timestamps from the same stable clock","Validate resolved minimum_release_age durations are sane before use","Prefer saturating conversion (unwrap_or(u64::MAX)) for time deltas"],"tags":["rust","time","duration","panic","overflow"],"backgroundTag":"value-out-of-range","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}