mastra-ai/mastra · error · FactoryRuleValidationError

Factory transition message must be an object.

Error message

Factory transition message must be an object.

What it means

For a 'transition' decision, the optional message field must be a plain object shaped { text, role? }. If message is present but not a plain object, this error is thrown. The message is later attached to the transition and validated further (exact keys, bounded text/role).

Source

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

      return {
        type,
        code: enumValue(value.code, REJECTION_CODES, 'Factory rejection code'),
        reason: boundedString(value.reason, 'Factory rejection reason', MAX_REASON_LENGTH),
      };
    }
    case 'transition': {
      assertExactKeys(
        value,
        ['type', 'idempotencyKey', 'board', 'stage', 'message', 'reenter'],
        'Factory transition decision',
      );
      if (value.reenter !== undefined && typeof value.reenter !== 'boolean') {
        throw new FactoryRuleValidationError('Factory transition reenter must be a boolean.');
      }
      let message: { text: string; role?: string } | undefined;
      if (value.message !== undefined) {
        if (!isPlainObject(value.message)) {
          throw new FactoryRuleValidationError('Factory transition message must be an object.');
        }
        assertExactKeys(value.message, ['text', 'role'], 'Factory transition message');
        const role =
          value.message.role === undefined
            ? undefined
            : boundedString(value.message.role, 'Factory transition message role', MAX_ROLE_LENGTH, IDENTIFIER_RE);
        message = {
          text: boundedString(value.message.text, 'Factory transition message text', MAX_MESSAGE_LENGTH),
          ...(role ? { role } : {}),
        };
      }
      return {
        type,
        ...commonCommitFields(value),
        board: enumValue(value.board, FACTORY_RULE_BOARDS, 'Factory transition board'),
        stage: enumValue(value.stage, FACTORY_RULE_STAGES, 'Factory transition stage'),
        ...(message ? { message } : {}),
        ...(value.reenter === true ? { reenter: true } : {}),

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Wrap text: message: { text: 'Stage updated' } optionally with role: { text, role: 'system' }
  2. Omit the message key entirely instead of passing null
  3. Convert SDK/framework message objects into the plain { text, role? } shape before calling decision()
  4. Pre-check with isPlainObject-like guard: v && typeof v === 'object' && !Array.isArray(v)

Example fix

// before
decision({ type: 'transition', ..., message: `Moved to ${stage}` })
// after
decision({ type: 'transition', ..., message: { text: `Moved to ${stage}` } })
Defensive patterns

Strategy: validation

Validate before calling

function assertTransitionMessage(d) {
  const m = (d as any)?.message;
  if (m !== undefined && (m === null || typeof m !== 'object' || Array.isArray(m))) {
    throw new TypeError('transition decision message must be { text, role? }');
  }
}

Type guard

const isTransitionMessage = (v): v is { text: string; role?: string } =>
  typeof v === 'object' && v !== null && !Array.isArray(v) && typeof (v as any).text === 'string';

Try / catch

try {
  return decision(d);
} catch (e) {
  if (e instanceof FactoryRuleValidationError && e.message === 'Factory transition message must be an object.') {
    logger.error('Bad transition message shape', { message: d.message });
    return decision({ ...d, message: typeof d.message === 'string' ? { text: d.message } : undefined });
  }
  throw e;
}

Prevention

When it happens

Trigger: decision({ type: 'transition', ..., message: 'hello' }) (string), message: ['hello'], message: new Message(...) (class instance), or message: null where null counts as defined-but-not-object.

Common situations: Passing a template string instead of { text: ... }; framework message objects (e.g. from another SDK) passed through unchanged; null from optional chaining where undefined was meant.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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