mastra-ai/mastra · error · Error
Foreach step cannot iterate a mapping: mappings project data
Error message
Foreach step cannot iterate a mapping: mappings project data, they don't execute per item. Use an agent, tool, or plain step as the foreach body.
What it means
A `foreach` entry's body must be an executable step (agent, tool, or plain step) that runs once per item. A `mapping` entry is a data projection — it reshapes input once, not per item — so using it as a foreach body is semantically invalid. The serializer rejects it at `toStorableGraph` time instead of producing a persisted workflow that behaves incorrectly on reload.
Source
Thrown at packages/core/src/workflows/dynamic/serialize.ts:60
case 'agent':
case 'tool':
case 'mapping':
return serializeSingleEntry(entry);
case 'sleep':
if (typeof entry.duration !== 'number') {
throw new Error(`Sleep step "${entry.id}" cannot be stored: dynamic duration (function) is not supported.`);
}
return { type: 'sleep', id: entry.id, duration: entry.duration };
case 'sleepUntil':
if (!(entry.date instanceof Date)) {
throw new Error(`SleepUntil step "${entry.id}" cannot be stored: dynamic date (function) is not supported.`);
}
return { type: 'sleepUntil', id: entry.id, date: entry.date };
case 'parallel':
return { type: 'parallel', steps: entry.steps.map(s => serializeSingleEntry(s)) };
case 'foreach':
if (entry.step.type === 'mapping') {
throw new Error(
`Foreach step cannot iterate a mapping: mappings project data, they don't execute per item. Use an agent, tool, or plain step as the foreach body.`,
);
}
return {
type: 'foreach',
step: serializeSingleEntry(entry.step),
opts:
typeof entry.opts.concurrency === 'function'
? { fn: entry.opts.concurrency.toString() }
: { concurrency: entry.opts.concurrency },
};
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.`,
);
}View on GitHub (pinned to 75dd419e61)
Solutions
- Use an agent, tool, or plain step as the foreach body; move the per-item projection into that step's logic or into a mapping step placed before/after the foreach.
- Restructure: `mapping` (project the list) -> `foreach(agent/tool/step)` -> `mapping` (collect results).
- Guard before persisting: `entry.type === 'foreach' && entry.step.type !== 'mapping'`.
Example fix
// before
foreach({ type: 'mapping', id: 'proj', mapConfig: {...} })
// after
foreach({ type: 'tool', id: 'process-item', toolId: 'myTool' }) Defensive patterns
Strategy: validation
Validate before calling
for (const e of stepFlow) {
if (e.type === 'foreach' && e.step.type === 'mapping') {
throw new Error('foreach body must be agent/tool/step, not a mapping');
}
} Type guard
const hasExecutableBody = (e: StepFlowEntry): boolean => e.type !== 'foreach' || (e.step.type === 'agent' || e.step.type === 'tool' || e.step.type === 'step');
Try / catch
try {
storable = toStorableGraph(stepFlow);
} catch (e) {
if (/Foreach step cannot iterate a mapping/.test(e.message)) {
// rebuild the foreach with an executable body
} else throw e;
} Prevention
- Remember: mapping projects data once; foreach executes per item.
- Put mappings before/after the foreach, not inside it.
- Use the supported entry factories rather than hand-assembling entries.
When it happens
Trigger: Building a workflow where `.foreach()` (or the dynamic `foreach` entry constructor) is given a `mapping`-typed step entry as its body, then calling `toStorableGraph` on the resulting `stepFlow`.
Common situations: Developers want to transform each item of a list and reach for a mapping step inside foreach, confusing 'project data' with 'execute per item'. Usually surfaces when persisting a dynamically built batch-processing workflow.
Related errors
- SleepUntil step "${entry.id}" cannot be stored: dynamic date
- Conditional (branch) step cannot be stored: closure predicat
- Loop step "${getSingleStepEntryId(entry.step)}" cannot be st
- Unknown step entry type: ${JSON.stringify(_exhaustive)}
- Mapping step "${entry.id}" cannot be stored: the function fo
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/532b6ad26b25f72e.
Report an issue: GitHub.