{"record":{"id":"6af2a06b1465b8dc","repo":"mastra-ai/mastra","slug":"rule-metadata-must-contain-finite-numbers","errorCode":null,"errorMessage":"Rule metadata must contain finite numbers.","messagePattern":"Rule metadata must contain finite numbers\\.","errorType":"validation","errorClass":"FactoryRuleValidationError","httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/rules/validation.ts","lineNumber":99,"sourceCode":"  if (value === undefined) return undefined;\n  return boundedString(value, label, max);\n}\n\nfunction enumValue<T extends string>(value: unknown, allowed: readonly T[], label: string): T {\n  if (typeof value !== 'string' || !allowed.includes(value as T)) {\n    throw new FactoryRuleValidationError(`${label} is invalid.`);\n  }\n  return value as T;\n}\n\nexport function normalizeFactoryRuleJsonValue(\n  value: unknown,\n  depth = 0,\n  seen = new Set<object>(),\n): FactoryRuleJsonValue {\n  if (value === null || typeof value === 'boolean' || typeof value === 'string') return value;\n  if (typeof value === 'number') {\n    if (!Number.isFinite(value)) throw new FactoryRuleValidationError('Rule metadata must contain finite numbers.');\n    return value;\n  }\n  if (depth >= MAX_JSON_DEPTH || (typeof value !== 'object' && !Array.isArray(value))) {\n    throw new FactoryRuleValidationError('Rule metadata is not bounded JSON.');\n  }\n  if (seen.has(value as object)) throw new FactoryRuleValidationError('Rule metadata must not contain cycles.');\n  seen.add(value as object);\n  try {\n    if (Array.isArray(value)) {\n      if (value.length > MAX_JSON_COLLECTION_SIZE) {\n        throw new FactoryRuleValidationError('Rule metadata contains too many entries.');\n      }\n      return value.map(entry => normalizeFactoryRuleJsonValue(entry, depth + 1, seen));\n    }\n    if (!isPlainObject(value)) throw new FactoryRuleValidationError('Rule metadata must use plain objects.');\n    const entries = Object.entries(value);\n    if (entries.length > MAX_JSON_COLLECTION_SIZE) {\n      throw new FactoryRuleValidationError('Rule metadata contains too many fields.');","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/rules/validation.ts#L81-L117","documentation":"Rule metadata (the metadata field of an upsertLinkedWorkItem decision) must be JSON-serializable with finite numbers only. normalizeFactoryRuleJsonValue rejects NaN, Infinity, and -Infinity because they do not survive JSON.stringify (they become null) and indicate numeric bugs. The library throws this before persisting metadata.","triggerScenarios":"Passing metadata with values like { attempts: NaN } or { ratio: 1/0 }, often the result of division by zero, parseInt on a bad string, or undefined arithmetic in the rule code producing NaN.","commonSituations":"Computing durations/ratios in rule handlers where a denominator is 0, parsing malformed numeric input with Number() returning NaN, spreading objects where a field was lost and NaN results.","solutions":["Check Number.isFinite(value) before placing computed numbers into metadata, and substitute a fallback (e.g. 0 or null).","Replace NaN/Infinity with null (null is allowed) or a sentinel string.","Fix the upstream arithmetic (guard division by zero, validate parsed input).","Sanitize metadata through a JSON round-trip JSON.parse(JSON.stringify(value)) and inspect what becomes null."],"exampleFix":"// before\nmetadata: { coverage: passed / total }\n// after\nmetadata: { coverage: total > 0 ? passed / total : null }","handlingStrategy":"validation","validationCode":"const assertFiniteNumbers = (obj: unknown, depth = 0): void => {\n  if (typeof obj === 'number' && !Number.isFinite(obj)) throw new RangeError('metadata contains non-finite number');\n  if (obj && typeof obj === 'object') for (const v of Object.values(obj)) assertFiniteNumbers(v, depth + 1);\n};\nassertFiniteNumbers(metadata);","typeGuard":"const isFiniteNumber = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);","tryCatchPattern":"try {\n  decisions.push({ type: 'upsertLinkedWorkItem', metadata, /* ... */ });\n} catch (e) {\n  if (e instanceof FactoryRuleValidationError && e.message.includes('finite numbers')) {\n    metadata = JSON.parse(JSON.stringify(metadata, (k, v) => (typeof v === 'number' && !Number.isFinite(v) ? null : v)));\n  } else throw e;\n}","preventionTips":["Guard division by zero and Number()/parseInt results before storing them in metadata.","Use null (allowed) instead of NaN/Infinity for missing numeric values.","Run metadata through Number.isFinite checks in a shared sanitize helper.","Prefer JSON.parse(JSON.stringify(...)) round-trip in tests to catch non-JSON numbers early."],"tags":["validation","json","numbers"],"backgroundTag":"non-serializable-value","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}