affaan-m/ECC · error · Error

${label} must be an ISO-8601 timestamp.

Error message

${label} must be an ISO-8601 timestamp.

What it means

Thrown by validateTimestamp() when a timestamp field (createdAt or updatedAt) does not match the strict ISO-8601 pattern ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$ AND round-trip exactly through new Date(x).toISOString(). The library requires millisecond precision and a trailing 'Z' (UTC) so timestamps are unambiguous and sort lexically. Any deviation is rejected rather than silently coerced.

Source

Thrown at scripts/lib/memory-vault-format.js:139

  }
  return values.reduce((result, value) => {
    const normalized = validator(value);
    if (result.includes(normalized)) {
      throw new Error(`${label} must not contain duplicate values.`);
    }
    return [...result, normalized];
  }, []);
}

function validateTimestamp(value, label) {
  const normalized = asNonEmptyString(value, label, 64);
  const parsed = new Date(normalized);
  if (
    !ISO_TIMESTAMP_PATTERN.test(normalized)
    || Number.isNaN(parsed.getTime())
    || parsed.toISOString() !== normalized
  ) {
    throw new Error(`${label} must be an ISO-8601 timestamp.`);
  }
  return normalized;
}

function normalizeBody(value) {
  if (typeof value !== 'string') {
    throw new Error('memory body must be a string.');
  }
  if (hasUnsafeControlCharacters(value, true)) {
    throw new Error('memory body must not contain unsafe control or bidirectional formatting characters.');
  }
  const normalized = value.trim();
  if (normalized.length === 0) {
    throw new Error('memory body must contain non-whitespace context.');
  }
  if (Buffer.byteLength(normalized, 'utf8') > MAX_BODY_BYTES) {
    throw new Error(`memory body is too large (maximum ${MAX_BODY_BYTES} bytes).`);
  }

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Pass new Date().toISOString() verbatim — it always emits the exact format required.
  2. If you have an epoch number, wrap it: new Date(epochMs).toISOString().
  3. If you have an offset timestamp, normalize first: new Date('2024-01-01T00:00:00.000+00:00').toISOString().
  4. Stop stripping the .SSS portion; the round-trip check fails without it.
  5. When backfilling, run each candidate through new Date(s).toISOString() === s before passing it in.

Example fix

// before
saveMemory({
  title: 'handoff',
  createdAt: '2024-08-12T14:30:00Z',          // missing .SSS
  updatedAt: Date.now(),                        // number, not string
  body: '...'
});

// after
const now = new Date().toISOString();
saveMemory({
  title: 'handoff',
  createdAt: now,                               // 2024-08-12T14:30:00.123Z
  updatedAt: now,
  body: '...'
});
Defensive patterns

Strategy: validation

Validate before calling

const ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
function isValidVaultTimestamp(value) {
  return typeof value === 'string'
    && ISO.test(value)
    && !Number.isNaN(new Date(value).getTime())
    && new Date(value).toISOString() === value;
}
if (!isValidVaultTimestamp(input.createdAt)) {
  input.createdAt = new Date().toISOString();
}

Type guard

function isIsoTimestamp(value): value is string {
  return typeof value === 'string'
    && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value)
    && new Date(value).toISOString() === value;
}

Try / catch

try {
  saveMemory(input);
} catch (err) {
  if (/must be an ISO-8601 timestamp/.test(err.message)) {
    input.createdAt = new Date().toISOString();
    input.updatedAt = new Date().toISOString();
    saveMemory(input);
  } else throw err;
}

Prevention

When it happens

Trigger: saveMemory({createdAt: '2024-01-01T00:00:00Z'}) (no milliseconds), saveMemory({createdAt: '2024-01-01T00:00:00.000+00:00'}) (offset instead of Z), saveMemory({createdAt: Date.now()}) (number not string), saveMemory({createdAt: '2024-13-45T99:99:99.999Z'}) (invalid date that still matches regex but fails Date parse), or saveMemory({updatedAt: someDate.toISOString().replace('.000Z','Z')}) which strips the round-trip requirement.

Common situations: Migrating from a system that stored epoch millis or SQL DATETIME strings. Using moment/date-fns default format() output. Copy-pasting a timestamp from a log file that uses second precision. Manually constructing timestamps via string concatenation. Frontend code that runs toISOString().replace(...) to trim what looked like redundant precision.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/d23ae71244204e7e. Report an issue: GitHub.