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-isView on GitHub (pinned to 75dd419e61)
Solutions
- Validate the numeric value before passing: Number.isFinite(v) && v >= 0.
- Parse env vars safely: const v = Number(raw); if (!Number.isFinite(v) || v < 0) throw; then pass v.
- 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
- Guard env-derived numbers with Number.isFinite and >= 0 checks.
- Prefer unit-suffixed strings ('30s', '1h') over raw numbers for readability.
- Never compute durations with operations that can yield NaN/Infinity from unset inputs.
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
- Google RBAC roleMapping is required.
- Cookie password must be at least 32 characters. Set OKTA_COO
- heartbeatMs must be a finite number no greater than ${MAX_TI
- Expected a PEM-encoded public key or certificate string for
- terminationGraceMs must be greater than zero.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/9a541392c0545f22.
Report an issue: GitHub.