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
- Remove the leading minus from the duration (e.g. `-90d` → `90d`)
- Provide a plain integer of seconds if you intend an absolute duration
- 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
- Never prefix duration flags with a minus
- Sanitize interpolated duration variables in scripts
- Prefer absolute integers (seconds) in automation
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
- a repository preview directory requires a dry run
- `--all` needs `--to
- at least one target or --changed is required
- cannot replace task stub directory because
- --changed does not accept target arguments
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)