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

  1. Rebuild the workflow using the declarative conditional API (createWorkflow branch/condition builders) so predicates serialize, then re-persist the graph.
  2. Check the stored graph JSON: ensure `predicates` exists on the conditional entry, has exactly one predicate per entry in `steps`, and no null entries.
  3. If migrating from an older stored format, re-export/re-serialize the workflow with the current serializer version before rehydrating.
  4. 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

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


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