mastra-ai/mastra · error

${path} must contain only finite numbers.

Error message

${path} must contain only finite numbers.

What it means

Workflow builder input must be JSON-serializable. normalizeJsonValue() recursively validates values, and any number that is NaN, +Infinity, or -Infinity fails Number.isFinite() and triggers this TypeError naming the offending path. NaN/Infinity cannot round-trip through JSON, so the builder rejects them at the boundary.

Source

Thrown at packages/core/src/workflows/builder/index.ts:130

  'tool',
  'mapping',
  'workflow',
  'parallel',
  'foreach',
  'sleep',
  'sleepUntil',
  'conditional',
  'loop',
] as const;

export type WorkflowBuilderSupportedStepType = (typeof WORKFLOW_BUILDER_SUPPORTED_STEP_TYPES)[number];

export { WORKFLOW_BUILDER_AUTHORING_CONSTRAINTS, WORKFLOW_BUILDER_AUTHORING_PLAYBOOK } from './authoring-playbook';

function normalizeJsonValue(value: unknown, path: string, seen: Set<object>): WorkflowBuilderJsonValue {
  if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;
  if (typeof value === 'number') {
    if (!Number.isFinite(value)) throw new TypeError(`${path} must contain only finite numbers.`);
    return value;
  }
  if (typeof value !== 'object') throw new TypeError(`${path} must be JSON-safe.`);
  if (seen.has(value)) throw new TypeError(`${path} must not contain cycles.`);
  seen.add(value);
  try {
    if (Array.isArray(value)) return value.map((item, index) => normalizeJsonValue(item, `${path}.${index}`, seen));
    if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
      throw new TypeError(`${path} must contain only plain objects.`);
    }
    const normalized: WorkflowBuilderJsonObject = {};
    for (const [key, item] of Object.entries(value)) {
      if (item !== undefined) normalized[key] = normalizeJsonValue(item, `${path}.${key}`, seen);
    }
    return normalized;
  } finally {
    seen.delete(value);
  }

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Sanitize inputs with Number.isFinite checks and replace non-finite values with a defined fallback (0, null, or a sentinel)
  2. Trace the reported `path` to find which field holds NaN/Infinity and fix the producing computation
  3. If infinity is meaningful, encode it as a string or { finite: false } marker the schema accepts
  4. Clamp or guard the arithmetic (avoid division by zero, coerce parseInt with Number.isNaN fallback)

Example fix

// before
const rate = total / count; // count = 0 → Infinity
builder.withConfig({ rate });
// after
const rate = count > 0 ? total / count : 0;
if (!Number.isFinite(rate)) throw new Error('invalid rate');
builder.withConfig({ rate });
Defensive patterns

Strategy: validation

Validate before calling

function assertFiniteNumbers(obj, path = 'input') {
  for (const [k, v] of Object.entries(obj)) {
    if (typeof v === 'number' && !Number.isFinite(v)) throw new Error(`${path}.${k} is not finite`);
    if (v && typeof v === 'object') assertFiniteNumbers(v, `${path}.${k}`);
  }
}
assertFiniteNumbers(params);

Type guard

const isFiniteNumber = (v) => typeof v === 'number' && Number.isFinite(v);

Try / catch

try {
  builder.withParams(raw);
} catch (e) {
  if (e instanceof TypeError && e.message.includes('must contain only finite numbers')) {
    logger.error('Non-finite number at', e.message.split(' ')[0]);
  } else throw e;
}

Prevention

When it happens

Trigger: Passing data containing NaN, Infinity, or -Infinity into workflow builder APIs (e.g. default/params/config values), typically from a division by zero, parseInt failure, or a Math op returning NaN.

Common situations: Computing defaults at runtime (e.g. 1/0, parseInt(undefined)); deserializing values from a source that encoded Infinity as a bare token; stats/averages over empty arrays yielding NaN.

Related errors


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