diff --git a/src/content/docs/workflows/build/rules-of-workflows.mdx b/src/content/docs/workflows/build/rules-of-workflows.mdx index fc6af378b8dddc7..0ff98965cee6060 100644 --- a/src/content/docs/workflows/build/rules-of-workflows.mdx +++ b/src/content/docs/workflows/build/rules-of-workflows.mdx @@ -205,6 +205,80 @@ export class MyWorkflow extends WorkflowEntrypoint { ``` +### Workflow code re-executes after hibernation + +When a Workflow hibernates and resumes, it's important to understand that **the entire workflow function runs again from the beginning**. However, completed steps return their cached results immediately without re-executing their logic. + +This means: +- All code outside of steps is re-executed +- Variables defined outside steps are re-initialized +- Non-deterministic values (like `Math.random()` or `Date.now()`) outside steps will produce different results +- Only values returned from `step.do()` calls are preserved across hibernations + + +```ts +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { + // 🔴 This random value will be different after hibernation + const randomOutside = Math.random() * 100; + + const capturedValue = await step.do("capture-value", async () => { + // ✅ This random value is preserved across hibernations + const randomInside = Math.random() * 100; + return randomInside; + }); + + // Long sleep that causes hibernation + await step.sleep("long-sleep", "3 hours"); + + await step.do("compare-values", async () => { + console.log("Random outside:", randomOutside); // New value after hibernation! + console.log("Captured value:", capturedValue); // Same value as before + }); + } +} +``` + + +This behavior is especially important for loops where iteration counters must be part of step returns: + + +```ts +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { + // 🔴 Bad: Loop counter will reset after hibernation + let iteration = 0; + while (iteration < 5) { + await step.do(`process-${iteration}`, async () => { + // After hibernation, iteration resets to 0 causing duplicate step names! + return { processed: iteration }; + }); + iteration++; + } + } +} +``` + + + +```ts +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { + // ✅ Good: Loop counter preserved in step returns + const initial = await step.do("init", async () => ({ iteration: 0 })); + let state = initial; + + while (state.iteration < 5) { + state = await step.do(`process-${state.iteration}`, async () => { + // state.iteration comes from previous step, survives hibernation + return { iteration: state.iteration + 1 }; + }); + } + } +} +``` + + ### Do not mutate your incoming events The `event` passed to your Workflow's `run` method is immutable: changes you make to the event are not persisted across steps and/or Workflow restarts.