mastra-ai/mastra · error · FactoryRuleValidationError
${label} is invalid.
Error message
${label} is invalid. What it means
After confirming the value is a string, boundedString rejects it if it is empty after trimming, exceeds the field's max length (e.g. idempotencyKey 256, message 8192, role 32, version 128), or fails the optional pattern (IDENTIFIER_RE for roles/keys, SKILL_NAME_RE for skill names). The message includes the field label so the developer knows which field failed.
Source
Thrown at mastracode/factory/src/rules/validation.ts:75
function isPlainObject(value: unknown): value is Record<string, unknown> {
if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
}
function assertExactKeys(value: Record<string, unknown>, keys: readonly string[], label: string): void {
const allowed = new Set(keys);
if (Object.keys(value).some(key => !allowed.has(key))) {
throw new FactoryRuleValidationError(`${label} contains an unsupported field.`);
}
}
function boundedString(value: unknown, label: string, max: number, pattern?: RegExp): string {
if (typeof value !== 'string') throw new FactoryRuleValidationError(`${label} must be a string.`);
const normalized = value.trim();
if (normalized.length === 0 || normalized.length > max || (pattern && !pattern.test(normalized))) {
throw new FactoryRuleValidationError(`${label} is invalid.`);
}
return normalized;
}
function optionalBoundedString(value: unknown, label: string, max: number): string | undefined {
if (value === undefined) return undefined;
return boundedString(value, label, max);
}
function enumValue<T extends string>(value: unknown, allowed: readonly T[], label: string): T {
if (typeof value !== 'string' || !allowed.includes(value as T)) {
throw new FactoryRuleValidationError(`${label} is invalid.`);
}
return value as T;
}
export function normalizeFactoryRuleJsonValue(
value: unknown,View on GitHub (pinned to 75dd419e61)
Solutions
- Trim the value yourself and check it is non-empty before passing it.
- For role/key fields, use /^[a-z0-9][a-z0-9_-]*$/i-compatible identifiers (letters, digits, underscore, hyphen only).
- For skillName, use lowercase kebab-case, e.g. 'run-tests'.
- Truncate long text to the documented max (message 8192, title 512, reason 512, arguments 4096) before building the decision.
Example fix
// before role: 'Backend Dev' // after role: 'backend-dev'
Defensive patterns
Strategy: validation
Validate before calling
const check = (s: string, max: number, re?: RegExp) => {
const t = s.trim();
if (!t || t.length > max || (re && !re.test(t))) throw new RangeError(`invalid ${s.slice(0, 20)}`);
};
check(role, 32, /^[a-z0-9][a-z0-9_-]*$/i); Type guard
const isValidIdentifier = (v: unknown): v is string => typeof v === 'string' && /^[a-z0-9][a-z0-9_-]*$/i.test(v.trim());
Try / catch
try {
validateFactoryRuleDecision(decision);
} catch (e) {
if (e instanceof FactoryRuleValidationError && e.message.endsWith('is invalid.')) {
throw new Error(`Constraint violation, fix value/format: ${e.message}`);
}
throw e;
} Prevention
- Trim and length-check user-derived strings before passing them to decisions.
- Use lowercase kebab-case for skill names and identifier-safe roles.
- Know the limits: idempotencyKey 256, role 32, reason/title 512, arguments 4096, message 8192, metadata key 128.
- Add unit tests that assert your rule-generated strings match the documented patterns.
When it happens
Trigger: Passing an empty or whitespace-only string; a role like 'My Role!' failing IDENTIFIER_RE; a skillName like 'My Skill' failing SKILL_NAME_RE (must be kebab-case lowercase); an oversized message (>8192 chars) or title (>512) or idempotencyKey (>256); a metadata key with characters outside [a-z0-9_-] or longer than 128.
Common situations: Whitespace-padded values from user input or clipboard paste, skill names with spaces or uppercase from hand-written config, very long error text stuffed into the message field, auto-generated ids with colons or slashes (UUID with dashes is fine, but ':' is not).
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
- ClaudeSDKAgent resumeData must include sessionId or continue
- CursorSDKAgent resumeData must include a message.
- CursorSDKAgent resumeData.agentId must be a string when prov
- CursorSDKAgent does not support structuredOutput because the
- OpenAISDKAgent resumeData must include a message.
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/adf8a57b153c37a9.
Report an issue: GitHub.