{"record":{"id":"209a67e4d92cdc0c","repo":"mastra-ai/mastra","slug":"loop-step-getsinglestepentryid-entry-step-ca","errorCode":null,"errorMessage":"Loop step \"${getSingleStepEntryId(entry.step)}\" cannot be stored: closure predicates do not round-trip. Use the declarative form ({ predicate: {...} }).","messagePattern":"Loop step \"(.+?)\" cannot be stored: closure predicates do not round-trip\\. Use the declarative form \\((.+?) \\}\\)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/workflows/dynamic/serialize.ts","lineNumber":89,"sourceCode":"      };\n    case 'conditional': {\n      const predicates = entry.predicates;\n      if (!predicates || predicates.some(p => !p || typeof p !== 'object')) {\n        throw new Error(\n          `Conditional (branch) step cannot be stored: closure predicates do not round-trip. Use the declarative form ({ predicate: {...} }) for each branch.`,\n        );\n      }\n      return {\n        type: 'conditional',\n        steps: entry.steps.map(s => serializeSingleEntry(s)),\n        serializedConditions: entry.serializedConditions,\n        predicates,\n      };\n    }\n    case 'loop': {\n      const predicate = entry.predicate;\n      if (!predicate || typeof predicate !== 'object') {\n        throw new Error(\n          `Loop step \"${getSingleStepEntryId(entry.step)}\" cannot be stored: closure predicates do not round-trip. Use the declarative form ({ predicate: {...} }).`,\n        );\n      }\n      return {\n        type: 'loop',\n        step: serializeSingleEntry(entry.step),\n        serializedCondition: entry.serializedCondition,\n        loopType: entry.loopType,\n        predicate,\n      };\n    }\n    default: {\n      const _exhaustive: never = entry;\n      throw new Error(`Unknown step entry type: ${JSON.stringify(_exhaustive)}`);\n    }\n  }\n}\n","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/workflows/dynamic/serialize.ts#L71-L107","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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'`."],"exampleFix":"// before\nloop(processStep, (data) => data.done !== true)\n\n// after\nloop(processStep, { predicate: { path: 'done', operator: 'neq', value: true } })","handlingStrategy":"validation","validationCode":"for (const e of stepFlow) {\n  if (e.type === 'loop' && (!e.predicate || typeof e.predicate !== 'object')) {\n    throw new Error(`loop \"${e.step?.id}\" needs a declarative { predicate: {...} }`);\n  }\n}","typeGuard":"const hasDeclarativeLoopPredicate = (e: StepFlowEntry): e is StepFlowEntry & { type: 'loop'; predicate: object } =>\n  e.type === 'loop' && !!e.predicate && typeof e.predicate === 'object';","tryCatchPattern":"try {\n  storable = toStorableGraph(stepFlow);\n} catch (e) {\n  if (/Loop step .* cannot be stored/.test(e.message)) {\n    // replace the closure predicate with a declarative one and retry\n  } else throw e;\n}","preventionTips":["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."],"tags":["workflows","serialization","closure","loop"],"backgroundTag":"non-serializable-closure","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}