mastra-ai/mastra · error

Union must have at least 2 options

Error message

Union must have at least 2 options

What it means

defaultZodUnionHandler in schema-compatibility-v3 rebuilds Zod unions after processing each option, then asserts at least 2 options exist since z.union() requires a tuple of 2+. This guard fires when the processed union somehow ended up with fewer than 2 options.

Source

Thrown at packages/schema-compat/src/schema-compatibility-v3.ts:438

    }

    const description = this.mergeParameterDescription(value.description, constraints);
    if (description) {
      result = result.describe(description);
    }
    return result;
  }

  /**
   * Default handler for Zod union types. Processes all union options.
   *
   * @param value - The Zod union to process
   * @returns The processed Zod union
   * @throws Error if union has fewer than 2 options
   */
  public defaultZodUnionHandler(value: ZodUnion<[ZodTypeAny, ...ZodTypeAny[]]>): ZodTypeAny {
    const processedOptions = value._def.options.map((option: ZodTypeAny) => this.processZodType(option));
    if (processedOptions.length < 2) throw new Error('Union must have at least 2 options');
    let result = z.union(processedOptions as [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]]);
    if (value.description) {
      result = result.describe(value.description);
    }
    return result;
  }

  /**
   * Default handler for Zod string types. Processes string validation constraints.
   *
   * @param value - The Zod string to process
   * @param handleChecks - String constraints to convert to descriptions vs keep as validation
   * @returns The processed Zod string
   */
  public defaultZodStringHandler(
    value: ZodString,
    handleChecks: readonly StringCheckType[] = ALL_STRING_CHECKS,
  ): ZodString {

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Ensure the union has at least two distinct options, e.g. z.union([z.string(), z.number()]).
  2. If only one option remains, use that option directly instead of wrapping it in z.union().
  3. If options are built at runtime, filter/branch so single-option arrays become the plain type.
  4. Verify you are not mis-tagging a non-union type as a union before the handler.

Example fix

// before
const schema = z.union([z.string()]);
// after
const schema = z.string(); // or add a second option to the union
Defensive patterns

Strategy: validation

Validate before calling

function toUnionOrSingle(types: z.ZodTypeAny[]): z.ZodTypeAny {
  if (types.length === 0) throw new TypeError('Need at least one type');
  return types.length === 1 ? types[0] : z.union(types as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]]);
}

Type guard

function isValidUnion(u: z.ZodUnion<[z.ZodTypeAny, ...z.ZodTypeAny[]]>): boolean {
  return u._def.options.length >= 2;
}

Try / catch

try {
  processed = compat.process(schema);
} catch (e) {
  if (e.message === 'Union must have at least 2 options') {
    throw new Error('Schema builds a union with <2 options; unwrap single-option unions', { cause: e });
  }
  throw e;
}

Prevention

When it happens

Trigger: Passing a union with fewer than two options into the compatibility layer — typically a TypeScript edge case where a union type collapsed to a single member, or building a union dynamically at runtime from an array that yielded one option.

Common situations: Runtime-constructed schemas where `options.length === 1` (should have been the bare type, not a union); refactors that removed a union member; wrapped/generated schemas mis-typed as ZodUnion.

Related errors


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