jdx/mise · error

duration must not be negative

Error message

duration must not be negative: {}

What it means

`parse_duration` converts a human duration string (via the `jiff` `Span` parser) or raw seconds into a non-negative `std::time::Duration`. Because negative durations make no sense for minimum-release-age style options, a parsed span that yields a negative duration is rejected with the offending input echoed.

Solutions

  1. Remove the leading minus from the duration (e.g. `-90d` → `90d`)
  2. Provide a plain integer of seconds if you intend an absolute duration
  3. Check the flag/config source for an unintended sign

Example fix

// before
mise outdated --min-age -90d
// after
mise outdated --min-age 90d
Defensive patterns

Strategy: validation

Validate before calling

function isValidMinAge(s) {
  return /^(?:\d+(?:\.\d+)?(?:s|m|h|d|w|mo|y)|\d+)$/.test(s.trim()); // positive span or integer seconds
}
// e.g. "90d", "3600" ok; "-90d" rejected before calling mise

Try / catch

const { stdout } = child.spawnSync('mise', ['outdated', '--min-age', flag], { encoding: 'utf8' });
if (/duration must not be negative/.test(stdout)) throw new Error(`negative min-age: ${flag}`);

Prevention

When it happens

Trigger: Calling `parse_duration` (via `resolve_cli_minimum_release_age` or `resolve_before_date_with_excludes`) with a relative span like `"-90d"` or `"-1y"` that resolves to a negative duration. Note the fallback path parses plain integers as seconds and is not covered by this check.

Common situations: Passing a negative duration to a CLI flag like `--min-age`; sign typo (`-30d` instead of `30d`); scripting that interpolates a negative variable.

Understand the failure class

Background: "invalid duration" / "failed to parse duration": why your timeout, interval, or TTL string is rejected and which formats each library accepts — this error's family across 32 libraries.

Related errors


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

Appendix: 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(crate) fn process_now() -> Timestamp {
    static PROCESS_NOW: OnceLock<Timestamp> = OnceLock::new();
    *PROCESS_NOW.get_or_init(Timestamp::now)
}

pub(crate) 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(crate) fn parse_into_timestamp(s: &str) -> Result<Timestamp> {
    // Try RFC3339 timestamp first
    if let Ok(ts) = s.parse::<Timestamp>() {

View on GitHub (pinned to 533346cc37)