{"record":{"id":"5a8488485bf48309","repo":"mastra-ai/mastra","slug":"sleep-step-entry-id-cannot-be-stored-dynamic","errorCode":null,"errorMessage":"Sleep step \"${entry.id}\" cannot be stored: dynamic duration (function) is not supported.","messagePattern":"Sleep step \"(.+?)\" cannot be stored: dynamic duration \\(function\\) is not supported\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/workflows/dynamic/serialize.ts","lineNumber":48,"sourceCode":"/**\n * Walk a live `stepFlow` and emit a JSON-safe `SerializedStepFlowEntry[]` with\n * full (un-truncated) mapping configs and all step/agent/tool references stored\n * as ids. Throws on entries that can't round-trip (closures, closure predicates).\n */\nexport function toStorableGraph(stepFlow: StepFlowEntry[]): SerializedStepFlowEntry[] {\n  return stepFlow.map(entry => serializeEntry(entry));\n}\n\nfunction serializeEntry(entry: StepFlowEntry): SerializedStepFlowEntry {\n  switch (entry.type) {\n    case 'step':\n    case 'agent':\n    case 'tool':\n    case 'mapping':\n      return serializeSingleEntry(entry);\n    case 'sleep':\n      if (typeof entry.duration !== 'number') {\n        throw new Error(`Sleep step \"${entry.id}\" cannot be stored: dynamic duration (function) is not supported.`);\n      }\n      return { type: 'sleep', id: entry.id, duration: entry.duration };\n    case 'sleepUntil':\n      if (!(entry.date instanceof Date)) {\n        throw new Error(`SleepUntil step \"${entry.id}\" cannot be stored: dynamic date (function) is not supported.`);\n      }\n      return { type: 'sleepUntil', id: entry.id, date: entry.date };\n    case 'parallel':\n      return { type: 'parallel', steps: entry.steps.map(s => serializeSingleEntry(s)) };\n    case 'foreach':\n      if (entry.step.type === 'mapping') {\n        throw new Error(\n          `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.`,\n        );\n      }\n      return {\n        type: 'foreach',\n        step: serializeSingleEntry(entry.step),","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/workflows/dynamic/serialize.ts#L30-L66","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nwf.sleep(ctx => jitter(1000, 5000)); // cannot store\n\n// after\nwf.sleep(3000); // static duration round-trips","handlingStrategy":"validation","validationCode":"function assertStorableSleeps(wf) {\n  for (const e of wf.getStepGraph?.() ?? []) {\n    if (e.type === 'sleep' && typeof e.duration !== 'number') {\n      throw new Error(`Sleep step \"${e.id}\" uses a dynamic duration; replace with a number before storing`);\n    }\n    if (e.type === 'sleepUntil' && !(e.date instanceof Date)) {\n      throw new Error(`sleepUntil \"${e.id}\" uses a dynamic date; replace with a Date before storing`);\n    }\n  }\n}","typeGuard":"function isStaticSleep(e: { type: string; duration?: unknown }): e is { type: 'sleep'; duration: number } {\n  return e.type === 'sleep' && typeof e.duration === 'number';\n}","tryCatchPattern":"try {\n  const stored = toStorableGraph(wf);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('dynamic duration (function) is not supported')) {\n    // rebuild workflow with a static sleep duration before persisting\n  }\n  throw err;\n}","preventionTips":["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."],"tags":["workflow","serialization","sleep","dynamic-duration"],"backgroundTag":"unserializable-dynamic-workflow-step","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}