mastra-ai/mastra · error · FactoryRuleValidationError

Factory decisions require unique idempotency keys.

Error message

Factory decisions require unique idempotency keys.

What it means

Every decision in a validated batch must carry a distinct idempotencyKey. After validating each decision, validateFactoryRuleDecisions compares the Set size of the collected keys against the array length and throws on any duplicate, since idempotency keys are used to dedupe/track committed work — duplicates would silently collapse or double-apply decisions.

Source

Thrown at mastracode/factory/src/rules/validation.ts:372

      throw new FactoryRuleValidationError('Factory rule decision type is unsupported.');
  }
}

export function validateFactoryRuleDecisions(values: readonly unknown[], causalDepth = 0): FactoryCommitDecision[] {
  if (values.length > MAX_JSON_COLLECTION_SIZE) {
    throw new FactoryRuleValidationError('Factory rule produced too many decisions.');
  }
  const decisions: FactoryCommitDecision[] = [];
  for (const value of values) {
    const decision = validateFactoryRuleDecision(value, causalDepth);
    if (decision.type === 'reject') {
      throw new FactoryRuleValidationError('A rejection cannot be persisted with commit decisions.');
    }
    decisions.push(decision);
  }
  const keys = decisions.map(decision => decision.idempotencyKey);
  if (new Set(keys).size !== keys.length) {
    throw new FactoryRuleValidationError('Factory decisions require unique idempotency keys.');
  }
  return decisions;
}

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Make each idempotencyKey unique by including the discriminating identifiers (item id, event id, timestamp/nonce).
  2. Deduplicate decisions before returning (e.g. key by idempotencyKey in a Map and emit the last/first winner).
  3. For genuinely repeated work, keep one decision and merge the payloads instead of emitting duplicates.

Example fix

// before
issues.map(i => ({ type: 'link', idempotencyKey: 'link-issue', ... }))
// after
issues.map(i => ({ type: 'link', idempotencyKey: `link-issue:${i.id}`, ... }))
Defensive patterns

Strategy: validation

Validate before calling

function checkUniqueKeys(decisions) {
  const keys = decisions.map(d => d.idempotencyKey);
  if (new Set(keys).size !== keys.length) {
    throw new Error('duplicate idempotencyKey in decision batch');
  }
}

Type guard

null

Try / catch

try {
  return validateFactoryRuleDecisions(decisions);
} catch (e) {
  if (e instanceof FactoryRuleValidationError && /unique idempotency keys/.test(e.message)) {
    const seen = new Set();
    return validateFactoryRuleDecisions(decisions.filter(d => !seen.has(d.idempotencyKey) && seen.add(d.idempotencyKey)));
  }
  throw e;
}

Prevention

When it happens

Trigger: A rule emitting multiple decisions where two or more share the same idempotencyKey — e.g. building keys from a coarse template like `${board}:${stage}` without including the item id, or reusing the same constant key in a loop.

Common situations: Loop-generated decisions that forgot to interpolate the loop item into the key; rules processing multiple events whose key template omits the event id; copy-pasted decision constructors with a hardcoded key.

Related errors


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