mastra-ai/mastra · error · Error
Cannot rehydrate conditional step: missing or mismatched pre
Error message
Cannot rehydrate conditional step: missing or mismatched predicates. Only declarative predicate branches round-trip.
What it means
When rehydrating a stored dynamic workflow graph, a 'conditional' entry must carry a `predicates` array that is present, the same length as `entry.steps`, and contains no null/undefined entries. Predicates are the declarative branch conditions attached to each branch of a conditional step; non-declarative (function-based or missing) predicates cannot be serialized, so the stored graph cannot be round-tripped. The library throws instead of silently dropping branches, because rehydrating a conditional without its predicates would change runtime behavior.
Source
Thrown at packages/core/src/workflows/dynamic/rehydrate.ts:160
case 'step': {
const live = rehydrateSingleEntry(entry, mastra, schemaOpts);
wf.__pushStepFlowEntry(live, entry);
return;
}
case 'workflow': {
const nested = assertWorkflowExists(mastra, entry.workflowId);
// A nested workflow executes as its own `Workflow`, so the engine keys its
// result by the workflow's intrinsic id. The portable definition addresses
// it by the declared call-site id, which is what mappings, predicates and
// `${stepResults...}` templates reference. Clone it under the declared id so
// every reference resolves instead of silently falling back to `initData`.
wf.then(entry.id && entry.id !== nested.id ? cloneWorkflow(nested as any, { id: entry.id }) : nested);
return;
}
case 'conditional': {
const predicates = entry.predicates;
if (!predicates || predicates.length !== entry.steps.length || predicates.some(p => !p)) {
throw new Error(
`Cannot rehydrate conditional step: missing or mismatched predicates. Only declarative predicate branches round-trip.`,
);
}
const steps = entry.steps.map(s => rehydrateSingleEntry(s, mastra, schemaOpts));
// Wire graphs may omit the Studio-facing condition labels; derive them
// from the predicates (same convention as the fluent builder).
const serializedConditions =
entry.serializedConditions ??
steps.map((s, i) => ({ id: `${getSingleStepEntryId(s)}-condition`, fn: derivePredicateLabel(predicates[i]!) }));
const live: StepFlowEntry = {
type: 'conditional',
steps,
conditions: predicates.map(p => predicateToCondition(p!)),
serializedConditions,
predicates,
};
wf.__pushStepFlowEntry(live, { ...entry, serializedConditions });
return;View on GitHub (pinned to 75dd419e61)
Solutions
- Rebuild the workflow using the declarative conditional API (createWorkflow branch/condition builders) so predicates serialize, then re-persist the graph.
- Check the stored graph JSON: ensure `predicates` exists on the conditional entry, has exactly one predicate per entry in `steps`, and no null entries.
- If migrating from an older stored format, re-export/re-serialize the workflow with the current serializer version before rehydrating.
- If branches are inherently dynamic (functions), keep the workflow defined in code rather than storing/rehydrating it.
Example fix
// stored entry (broken)
{ type: 'conditional', steps: [stepA, stepB] } // predicates missing
// fixed stored entry
{ type: 'conditional', steps: [stepA, stepB], predicates: [serializedConditionA, serializedConditionB] } Defensive patterns
Strategy: validation
Validate before calling
function hasRoundTrippablePredicates(entry) {
return entry.type !== 'conditional' ||
(Array.isArray(entry.predicates) &&
entry.predicates.length === entry.steps.length &&
entry.predicates.every(Boolean));
}
// run over stored graph entries before rehydrateWorkflow Type guard
function isConditionalEntry(e: unknown): e is { type: 'conditional'; steps: unknown[]; predicates: unknown[] } {
return typeof e === 'object' && e !== null && (e as any).type === 'conditional' &&
Array.isArray((e as any).predicates);
} Try / catch
try {
const wf = rehydrateWorkflow(stored, mastra);
} catch (err) {
if (err instanceof Error && err.message.includes('Cannot rehydrate conditional step')) {
// fall back to rebuilding the workflow from source definition
}
throw err;
} Prevention
- Always use the declarative condition builders for conditional branches, never inline function predicates.
- Re-serialize stored graphs after upgrading the library rather than reusing old persisted JSON.
- Validate stored graph JSON (predicates present, aligned with steps) before rehydration.
When it happens
Trigger: Calling rehydrateWorkflow with a stored graph entry whose `type === 'conditional'` where: entry.predicates is undefined/null, entry.predicates.length !== entry.steps.length, or any predicate element is falsy (p => !p). This happens when the workflow was built with non-declarative predicates (inline functions) that could not be serialized, or when the stored graph was hand-edited/partially persisted.
Common situations: Restoring a persisted dynamic workflow (e.g. from a database or stored graph JSON) whose conditional branches were defined with function predicates instead of the declarative condition builder; a serialization version that predates predicate persistence; manually edited stored graphs where a branch was added without adding its predicate.
Related errors
- Cannot rehydrate loop step: missing declarative predicate or
- Unknown stored step type: ${JSON.stringify(_exhaustive)}
- Dynamic workflow references agent "${entry.agentId}" which i
- Dynamic workflow references tool "${entry.toolId}" which is
- Dynamic workflow references step "${id}" which is not regist
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/6f7aa93de6e4387e.
Report an issue: GitHub.