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 the Zod v4 compatibility path rebuilds unions from `value._zod.def.options` and requires at least 2 options because z.union() needs a tuple of 2+. This guard throws when a union with fewer than 2 options reaches the handler.
Source
Thrown at packages/schema-compat/src/schema-compatibility-v4.ts:457
const legacyDescription = value.description;
const description = this.mergeParameterDescription(metaDescription || legacyDescription, 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<[ZodAny, ...ZodAny[]]>): ZodAny {
const processedOptions = value._zod.def.options.map((option: ZodAny) => this.processZodType(option));
if (processedOptions.length < 2) throw new Error('Union must have at least 2 options');
let result = z.union(processedOptions as [ZodAny, ZodAny, ...ZodAny[]]);
if (value.description) {
result = result.describe(value.description);
}
// @ts-expect-error - fix later
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,View on GitHub (pinned to 75dd419e61)
Solutions
- Provide at least two options in the union: z.union([a, b]).
- When only one branch exists, return the branch type itself, unwrapped.
- For runtime-built unions, branch on options.length before calling the compat layer.
- Use z.optional()/z.nullable() instead of one-sided unions to express optionality.
Example fix
// before const maybe = z.union([z.string()]); // after const maybe = z.union([z.string(), z.null()]); // or z.string().nullable()
Defensive patterns
Strategy: validation
Validate before calling
function toUnionOrSingleV4(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 isValidUnionV4(u: { _zod: { def: { options: unknown[] } } }): boolean {
return u._zod.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('Dynamic union collapsed; ensure >=2 branches or unwrap', { cause: e });
}
throw e;
} Prevention
- Guard dynamic option arrays before z.union.
- Replace one-branch unions with the branch itself or z.nullable/z.optional.
- Test generated schemas after Zod v3→v4 migration.
When it happens
Trigger: A ZodUnion whose definition contains 0 or 1 options after processing — usually from dynamically built unions, or a single-option union created at runtime in Zod v4.
Common situations: Generated schemas (e.g. from OpenAPI/JSON Schema converters) that collapse to one branch; code that spreads optional/filtered option arrays into z.union; version migrations from Zod v3 unions.
Related errors
- Schema validation failed due to an invalid schema definition
- Union must have at least 2 options
- ${this.model.modelId} does not support zod type: ${value.con
- Cannot flatten intersections with overlapping keys
- OBSERVABILITY_INVALID_CONFIG
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/3914132e703ff4a4.
Report an issue: GitHub.