mem0ai/mem0 · error · Error

expirationDate must be a valid date in YYYY-MM-DD format.

Error message

expirationDate must be a valid date in YYYY-MM-DD format.

What it means

Thrown by normalizeExpirationDate in the shared expiration utils when a value does not match YYYY-MM-DD or matches syntactically but is not a real calendar date. The regex match is re-validated by constructing a UTC Date and confirming the year, month, and day round-trip exactly. Expiration dates are stored as YYYY-MM-DD strings precisely so they sort lexicographically like dates.

Source

Thrown at mem0-ts/src/oss/src/utils/expiration.ts:36

 * Python SDK rejects ("12/31/2099", "2099") and resolves them against the
 * local timezone, shifting the calendar day. It also silently rolls invalid
 * dates over — `new Date("2099-02-30T00:00:00Z")` yields March 2nd.
 */
export function normalizeExpirationDate(value: string): string {
  const match = EXPIRATION_DATE_PATTERN.exec(value);
  if (match) {
    const [, year, month, day] = match;
    const parsed = new Date(`${value}T00:00:00Z`);
    if (
      !Number.isNaN(parsed.getTime()) &&
      parsed.getUTCFullYear() === Number(year) &&
      parsed.getUTCMonth() === Number(month) - 1 &&
      parsed.getUTCDate() === Number(day)
    ) {
      return value;
    }
  }
  throw new Error("expirationDate must be a valid date in YYYY-MM-DD format.");
}

/** True when the payload carries an expiration date strictly before today (UTC). */
export function payloadIsExpired(
  payload: Record<string, any> | null | undefined,
) {
  const raw = payload?.expiration_date;
  if (!raw) return false;
  try {
    // YYYY-MM-DD sorts lexicographically the same way it sorts chronologically.
    return normalizeExpirationDate(String(raw)) < todayUtc();
  } catch {
    // Unparseable stored value: treat as non-expiring rather than hiding data.
    return false;
  }
}

View on GitHub (pinned to 001c235229)

Solutions

  1. Format as YYYY-MM-DD: date.toISOString().slice(0, 10)
  2. Validate user-supplied dates before the call with the same round-trip check
  3. If the error comes from payloadIsExpired on stored data, fix the corrupted expiration_date values in the vector store payload

Example fix

// before
await memory.add('note', { userId: 'u1', expirationDate: new Date('2026-12-31').toISOString() });

// after
await memory.add('note', { userId: 'u1', expirationDate: '2026-12-31' });
// or: expirationDate: new Date('2026-12-31').toISOString().slice(0, 10)
Defensive patterns

Strategy: validation

Validate before calling

function toExpirationDate(d: Date | string): string {
  const s = typeof d === 'string' ? d : d.toISOString().slice(0, 10);
  const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
  if (!m) throw new Error(`invalid expirationDate: ${s}`);
  const [y, mo, da] = m.slice(1).map(Number);
  const t = new Date(Date.UTC(y, mo - 1, da));
  if (t.getUTCFullYear() !== y || t.getUTCMonth() !== mo - 1 || t.getUTCDate() !== da) {
    throw new Error(`not a real date: ${s}`);
  }
  return s;
}

Type guard

const isYmdDate = (s: string): boolean =>
  /^\d{4}-\d{2}-\d{2}$/.test(s) && !Number.isNaN(new Date(`${s}T00:00:00Z`).getTime());

Prevention

When it happens

Trigger: Passing expirationDate as '2024-13-01' (month 13), '2024-02-30' (nonexistent day), '2024/02/29' (wrong separators), '2024-1-5' (non-padded), or a full ISO timestamp like '2024-12-31T00:00:00Z'. Reachable via add/update config paths that normalize expiration dates, and indirectly by payloadIsExpired when stored payload values are malformed.

Common situations: Feeding new Date().toISOString() output (contains time and Z); user input from a date picker with a different format; locale-formatted dates; assuming partial dates like '2024-12' are accepted.

Related errors


AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15). Data as JSON: /api/errors/804128d88752dd7a. Report an issue: GitHub.