mastra-ai/mastra · error · Error
Sleep step "${entry.id}" cannot be stored: dynamic duration
Error message
Sleep step "${entry.id}" cannot be stored: dynamic duration (function) is not supported. What it means
During serialization (`toStorableGraph`), a 'sleep' entry whose duration is not a plain number — i.e. a function computing the duration at runtime — cannot be persisted, because only static durations round-trip through the stored graph format. The library throws at serialize time so dynamic sleeps are never silently converted to a wrong static value.
Source
Thrown at packages/core/src/workflows/dynamic/serialize.ts:48
/**
* Walk a live `stepFlow` and emit a JSON-safe `SerializedStepFlowEntry[]` with
* full (un-truncated) mapping configs and all step/agent/tool references stored
* as ids. Throws on entries that can't round-trip (closures, closure predicates).
*/
export function toStorableGraph(stepFlow: StepFlowEntry[]): SerializedStepFlowEntry[] {
return stepFlow.map(entry => serializeEntry(entry));
}
function serializeEntry(entry: StepFlowEntry): SerializedStepFlowEntry {
switch (entry.type) {
case 'step':
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),View on GitHub (pinned to 75dd419e61)
Solutions
- Replace the function duration with a fixed numeric duration if the value is known, then serialize.
- If the duration must be dynamic, keep that workflow code-defined instead of storing/rehydrating it.
- Move dynamic delay logic into a step that awaits a timer internally, with a static sleep in the stored graph.
- Pre-check before serializing: reject sleep entries whose duration is not a number and choose a static value.
Example fix
// before wf.sleep(ctx => jitter(1000, 5000)); // cannot store // after wf.sleep(3000); // static duration round-trips
Defensive patterns
Strategy: validation
Validate before calling
function assertStorableSleeps(wf) {
for (const e of wf.getStepGraph?.() ?? []) {
if (e.type === 'sleep' && typeof e.duration !== 'number') {
throw new Error(`Sleep step "${e.id}" uses a dynamic duration; replace with a number before storing`);
}
if (e.type === 'sleepUntil' && !(e.date instanceof Date)) {
throw new Error(`sleepUntil "${e.id}" uses a dynamic date; replace with a Date before storing`);
}
}
} Type guard
function isStaticSleep(e: { type: string; duration?: unknown }): e is { type: 'sleep'; duration: number } {
return e.type === 'sleep' && typeof e.duration === 'number';
} Try / catch
try {
const stored = toStorableGraph(wf);
} catch (err) {
if (err instanceof Error && err.message.includes('dynamic duration (function) is not supported')) {
// rebuild workflow with a static sleep duration before persisting
}
throw err;
} Prevention
- Use numeric sleep durations in workflows intended for storage/rehydration.
- Implement dynamic waits inside a custom step rather than a function-valued sleep.
- Add a lint/test that walks the step graph and rejects function durations before persisting.
When it happens
Trigger: Calling the store/serialize path (toStorableGraph -> serializeEntry) on a dynamic workflow containing `.sleep(durationFn)` where `typeof entry.duration !== 'number'` — a function or other non-number was passed as the sleep duration.
Common situations: Using `.sleep(ctx => computedMs)` for backoff/jitter and then attempting to persist the workflow; storing workflows built with runtime-computed sleeps for cross-service deployment.
Related errors
- Cannot rehydrate conditional step: missing or mismatched pre
- Cannot rehydrate loop step: missing declarative predicate or
- Unknown stored step type: ${JSON.stringify(_exhaustive)}
- ${describeBadPlaceholder(template, idx, rawExpr)} resolved t
- Workflow ${workflowId} not found
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/5a8488485bf48309.
Report an issue: GitHub.