jdx/mise · error · eyre::Report

duration must not be negative: {}

Error message

duration must not be negative: {}

What it means

`parse_duration` (src/duration.rs:36) first parses the string as a jiff `Span` (`90d`, `1y 6m`) and converts it relative to a fixed date. A negative span (leading `-`) yields a negative duration, which timeout/expiry semantics cannot use, so it bails; only if the string is not a span at all does it fall back to plain seconds parsing.

Source

Thrown at src/duration.rs:43

///
/// This is used for resolving relative durations (e.g. `minimum_release_age = "3d"`)
/// consistently: every resolution of the same relative duration within a single
/// mise invocation produces the same absolute timestamp, and downstream code
/// that converts the absolute timestamp back to a duration (e.g. for npm's
/// `--min-release-age`) gets the exact duration the user specified rather than
/// a slightly-larger value due to wall clock drift between phases.
pub fn process_now() -> Timestamp {
    static PROCESS_NOW: OnceLock<Timestamp> = OnceLock::new();
    *PROCESS_NOW.get_or_init(Timestamp::now)
}

pub fn parse_duration(s: &str) -> Result<Duration> {
    match s.parse::<Span>() {
        Ok(span) => {
            // we must provide a relative date to determine the duration with months and years
            let duration = span.to_duration(date(2025, 1, 1))?;
            if duration.is_negative() {
                bail!("duration must not be negative: {}", s);
            }
            Ok(duration.unsigned_abs())
        }
        Err(_) => Ok(Duration::from_secs(s.parse()?)),
    }
}

/// Parse a date/duration string into a Timestamp.
/// Supports:
/// - RFC3339 timestamps: "2024-06-01T12:00:00Z"
/// - ISO dates: "2024-06-01" (treated as end of day in UTC)
/// - Relative durations: "90d", "1y", "6m" (subtracted from now)
///
/// Relative durations are anchored to [`process_now`] so all resolutions
/// within a single mise invocation agree on "now".
pub fn parse_into_timestamp(s: &str) -> Result<Timestamp> {
    // Try RFC3339 timestamp first
    if let Ok(ts) = s.parse::<Timestamp>() {

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Drop the leading minus: `5m` instead of `-5m`
  2. If you meant 'as short as possible', use the smallest accepted positive duration or the setting's zero/minimum
  3. Check the setting's docs for whether span syntax (`90d`) or plain seconds is expected

Example fix

# before
[deps.providers.npm]
timeout = "-5m"

# after
[deps.providers.npm]
timeout = "5m"
Defensive patterns

Strategy: validation

Validate before calling

# reject negative durations before they reach mise
import re, sys
val = "5m"
if re.match(r"^-", val) or val.lstrip("-").strip() == "":
    sys.exit("duration must be positive")
print("ok")

Type guard

fn is_positive_span(s: &str) -> bool {
    s.parse::<jiff::Span>().ok()
        .and_then(|sp| sp.to_duration(jiff::civil::date(2025, 1, 1)).ok())
        .is_some_and(|d| !d.is_negative())
}

Prevention

When it happens

Trigger: Any setting parsed with `parse_duration` — e.g. deps provider `timeout` values (see the timeout parsing in src/deps/mod.rs that warns on invalid timeouts) — receiving a negative span string such as `"-5m"` or `"-1h30m"`.

Common situations: Pasting negative durations from deadline-style examples ("time until expiry") into a duration field; templated values that render `-<n>`; mixing up 'duration' with 'point in time' semantics and negating the value.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/13c16991eb291d9c. Report an issue: GitHub.