mastra-ai/mastra · error

Invalid retention duration: ${duration}. Must be a non-negat

Error message

Invalid retention duration: ${duration}. Must be a non-negative finite number of milliseconds.

What it means

parseDuration converts a Duration (number of milliseconds or a string like '30s'/'2h'/'7d') into milliseconds. For numeric input it requires a finite, non-negative value; anything else (NaN, Infinity, negative) throws this error. String inputs must match the unit regex or fail with a similar error.

Source

Thrown at packages/core/src/storage/utils.ts:64

  s: 1000,
  m: 60 * 1000,
  h: 60 * 60 * 1000,
  d: 24 * 60 * 60 * 1000,
  w: 7 * 24 * 60 * 60 * 1000,
};

/**
 * Parses a retention {@link Duration} into milliseconds.
 *
 * Accepts a raw number of milliseconds or a `<number><unit>` string where unit
 * is one of `ms`, `s`, `m`, `h`, `d`, `w`.
 *
 * @throws Error if the input is not a valid duration.
 */
export function parseDuration(duration: Duration): number {
  if (typeof duration === 'number') {
    if (!Number.isFinite(duration) || duration < 0) {
      throw new Error(`Invalid retention duration: ${duration}. Must be a non-negative finite number of milliseconds.`);
    }
    return duration;
  }

  const match = /^(\d+(?:\.\d+)?)(ms|s|m|h|d|w)$/.exec(duration);
  if (!match) {
    throw new Error(
      `Invalid retention duration: "${duration}". Expected a number of milliseconds or a "<number><unit>" string (ms, s, m, h, d, w).`,
    );
  }

  const value = Number(match[1]);
  const unit = match[2]!;
  return value * DURATION_UNIT_MS[unit]!;
}

export function safelyParseJSON(input: any): any {
  // If already an object (and not null), return as-is

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Validate the numeric value before passing: Number.isFinite(v) && v >= 0.
  2. Parse env vars safely: const v = Number(raw); if (!Number.isFinite(v) || v < 0) throw; then pass v.
  3. Use a valid duration string ('30s', '1h', '7d') instead of an invalid number if the API accepts strings.

Example fix

// before
const ttl = Number(process.env.RETENTION_MS); // NaN when unset
// after
const parsed = Number(process.env.RETENTION_MS ?? '604800000');
const ttl = Number.isFinite(parsed) && parsed >= 0 ? parsed : 604800000;
Defensive patterns

Strategy: validation

Validate before calling

function toValidMs(v: number | string): number | string {
  if (typeof v === 'number' && (!Number.isFinite(v) || v < 0)) {
    throw new Error(`Invalid duration: ${v}`);
  }
  return v;
}

Type guard

function isValidDuration(d: unknown): d is number {
  return typeof d === 'number' && Number.isFinite(d) && d >= 0
    || (typeof d === 'string' && /^\d+(?:\.\d+)?(ms|s|m|h|d|w)$/.test(d));
}

Try / catch

try {
  const ms = parseDuration(rawDuration);
} catch (e) {
  console.error('Bad retention config, falling back to 7d');
  const ms = parseDuration('7d');
}

Prevention

When it happens

Trigger: Passing a retention/TTL number that is negative, NaN, or Infinity to a storage/retention option that runs through parseDuration; reading the value from an env var with Number() yielding NaN.

Common situations: Number(process.env.RETENTION) when the env var is unset (NaN) or '-1'; computing a duration via division that produces Infinity; copying a value in the wrong unit (seconds instead of milliseconds) resulting in a negative after subtraction.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/9a541392c0545f22. Report an issue: GitHub.