mastra-ai/mastra · error · Error
Loop step "${getSingleStepEntryId(entry.step)}" cannot be st
Error message
Loop step "${getSingleStepEntryId(entry.step)}" cannot be stored: closure predicates do not round-trip. Use the declarative form ({ predicate: {...} }). What it means
A `loop` entry's `predicate` (the while/until condition controlling iteration) must be a declarative object to be storable. Closure predicates cannot be serialized, so `toStorableGraph` throws when the predicate is missing, null, or a function. The error names the loop's inner step id so the author can locate the offending loop.
Source
Thrown at packages/core/src/workflows/dynamic/serialize.ts:89
};
case 'conditional': {
const predicates = entry.predicates;
if (!predicates || predicates.some(p => !p || typeof p !== 'object')) {
throw new Error(
`Conditional (branch) step cannot be stored: closure predicates do not round-trip. Use the declarative form ({ predicate: {...} }) for each branch.`,
);
}
return {
type: 'conditional',
steps: entry.steps.map(s => serializeSingleEntry(s)),
serializedConditions: entry.serializedConditions,
predicates,
};
}
case 'loop': {
const predicate = entry.predicate;
if (!predicate || typeof predicate !== 'object') {
throw new Error(
`Loop step "${getSingleStepEntryId(entry.step)}" cannot be stored: closure predicates do not round-trip. Use the declarative form ({ predicate: {...} }).`,
);
}
return {
type: 'loop',
step: serializeSingleEntry(entry.step),
serializedCondition: entry.serializedCondition,
loopType: entry.loopType,
predicate,
};
}
default: {
const _exhaustive: never = entry;
throw new Error(`Unknown step entry type: ${JSON.stringify(_exhaustive)}`);
}
}
}
View on GitHub (pinned to 75dd419e61)
Solutions
- Convert the loop predicate to the declarative form `{ predicate: {...} }` (field/operator/value data) instead of a closure.
- If the condition needs arbitrary computation, move it into a plain step inside the loop that sets a flag, and loop on a declarative predicate over that flag.
- Keep the loop workflow unpersisted if a closure is truly required.
- Pre-validate: `entry.type === 'loop' && entry.predicate && typeof entry.predicate === 'object'`.
Example fix
// before
loop(processStep, (data) => data.done !== true)
// after
loop(processStep, { predicate: { path: 'done', operator: 'neq', value: true } }) Defensive patterns
Strategy: validation
Validate before calling
for (const e of stepFlow) {
if (e.type === 'loop' && (!e.predicate || typeof e.predicate !== 'object')) {
throw new Error(`loop "${e.step?.id}" needs a declarative { predicate: {...} }`);
}
} Type guard
const hasDeclarativeLoopPredicate = (e: StepFlowEntry): e is StepFlowEntry & { type: 'loop'; predicate: object } =>
e.type === 'loop' && !!e.predicate && typeof e.predicate === 'object'; Try / catch
try {
storable = toStorableGraph(stepFlow);
} catch (e) {
if (/Loop step .* cannot be stored/.test(e.message)) {
// replace the closure predicate with a declarative one and retry
} else throw e;
} Prevention
- Express loop exit conditions as field/operator/value data.
- Compute complex conditions inside a loop step that writes a flag, then loop on the flag declaratively.
- Pre-validate predicates before calling save/persist APIs.
When it happens
Trigger: Persisting a `stepFlow` containing a `loop` entry built with a function predicate (e.g. `.while((data) => data.attempts < 5)`) or with no predicate at all.
Common situations: Authors write loop exit conditions as arrow functions against loop data, which works at runtime but fails when the workflow is saved via the dynamic workflow persistence path (e.g. Studio-saved or API-stored workflows).
Related errors
- SleepUntil step "${entry.id}" cannot be stored: dynamic date
- Conditional (branch) step cannot be stored: closure predicat
- Mapping step "${entry.id}" cannot be stored: the function fo
- Mapping step "${entry.id}" key "${key}" cannot be stored: so
- ${kind === 'agent' ? 'Agent' : 'Tool'} step "${entryId}" can
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/209a67e4d92cdc0c.
Report an issue: GitHub.