mastra-ai/mastra · error · Error

Cannot rehydrate loop step: missing declarative predicate…

Error message

Cannot rehydrate loop step: missing declarative predicate or loopType. Only declarative predicate loops round-trip.

What it means

A stored 'loop' entry can only be rehydrated if it has a declarative (serializable) `predicate` and an explicit `loopType` of 'dowhile' or 'dountil'. Loops whose predicate is a runtime function cannot be serialized, so the library refuses to reconstruct them rather than guessing loop semantics and silently changing behavior.

Solutions

  1. Rebuild the loop with the declarative predicate API (serializable condition object) and re-serialize the workflow.
  2. Verify the stored loop entry contains both `predicate` and `loopType: 'dowhile' | 'dountil'`.
  3. Re-export stored graphs with the current serializer if they were produced by an older version.
  4. Keep function-predicate loops as code-defined workflows instead of stored/rehydrated dynamic workflows.

Example fix

// stored entry (broken)
{ type: 'loop', step: loopStep } // no predicate/loopType

// fixed stored entry
{ type: 'loop', step: loopStep, loopType: 'dowhile', predicate: { id: 'check', ... } }
Defensive patterns

Strategy: validation

Validate before calling

function hasRoundTrippableLoop(entry) {
  return entry.type !== 'loop' ||
    (Boolean(entry.predicate) && (entry.loopType === 'dowhile' || entry.loopType === 'dountil'));
}

Type guard

function isRehydratableLoop(e: unknown): e is { type: 'loop'; predicate: unknown; loopType: 'dowhile' | 'dountil'; step: unknown } {
  const x = e as any;
  return !!x && x.type === 'loop' && !!x.predicate && (x.loopType === 'dowhile' || x.loopType === 'dountil');
}

Try / catch

try {
  const wf = rehydrateWorkflow(stored, mastra);
} catch (err) {
  if (err instanceof Error && err.message.includes('Cannot rehydrate loop step')) {
    // rebuild loop with declarative predicate from source
  }
  throw err;
}

Prevention

When it happens

Trigger: rehydrateWorkflow encounters a stored graph entry with type 'loop' where entry.predicate is falsy, or entry.loopType is anything other than 'dowhile'/'dountil' (e.g. undefined because the original loop used a function predicate that failed to serialize).

Common situations: Persisting workflows built with `.dowhile(...)`/`.dountil(...)` using inline function predicates, then rehydrating them on another server or after restart; older stored graphs saved before declarative loop predicates were supported; hand-edited stored JSON dropping the loopType field.

Related errors


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

Appendix: source

Thrown at packages/core/src/workflows/dynamic/rehydrate.ts:183

      // 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;
    }
    case 'loop': {
      const { predicate, loopType } = entry;
      if (!predicate || (loopType !== 'dowhile' && loopType !== 'dountil')) {
        throw new Error(
          `Cannot rehydrate loop step: missing declarative predicate or loopType. Only declarative predicate loops round-trip.`,
        );
      }
      const step = rehydrateSingleEntry(entry.step, mastra, schemaOpts);
      const serializedCondition = entry.serializedCondition ?? {
        id: `${getSingleStepEntryId(step)}-condition`,
        fn: derivePredicateLabel(predicate),
      };
      const live: StepFlowEntry = {
        type: 'loop',
        step,
        condition: predicateToCondition(predicate),
        loopType,
        serializedCondition,
        predicate,
      };
      wf.__pushStepFlowEntry(live, { ...entry, serializedCondition });
      return;

View on GitHub (pinned to 75dd419e61)