UNPKG

workflow

Version:

Workflow SDK - Build durable, resilient, and observable workflows

161 lines (116 loc) • 5.68 kB
--- title: serialization-failed description: Ensure all data passed between workflow and step functions is serializable. type: troubleshooting summary: Resolve serialization errors by passing only serializable types across execution boundaries. prerequisites: - /docs/foundations/serialization related: - /docs/foundations/workflows-and-steps --- <CopyPrompt text="Fix Workflow serialization errors. Find the failing `start()` call, workflow argument, step return value, hook payload, or stream chunk. Replace non-serializable values such as class instances, functions, SDK clients, Response/Request objects, streams, database connections, Dates that need custom handling, Maps/Sets, or circular objects with plain JSON-compatible data, IDs, strings, numbers, booleans, arrays, and objects. Recreate runtime-only clients or objects inside `&quot;use step&quot;` helpers instead of passing them through the workflow log. For external resources, pass a stable ID or URL and load the resource inside the step. Add a test or local route call that serializes the same input/output path successfully." /> This error occurs when you try to pass non-serializable data between execution boundaries in your workflow. All data passed between workflow functions, step functions, and the workflow runtime must be serializable to persist in the event log. ## Error message ```text Failed to serialize workflow arguments. Ensure you're passing serializable types (plain objects, arrays, primitives, Date, RegExp, Map, Set). ``` This error can appear when: - Serializing workflow arguments when calling `start()` - Serializing workflow return values - Serializing step arguments - Serializing step return values ## Where the error surfaces Where you observe the failure depends on which boundary it crosses: - **Workflow arguments**: `start()` throws synchronously in your application code. - **Step arguments and step return values**: the *step* fails with the `SerializationError`, exactly like a step whose body threw a fatal error: no retries (the failure is deterministic), and a `try/catch` around the step call in your workflow code observes it. The step's recorded input shows `[input unavailable: step argument serialization failed]` when the arguments were the unserializable part. - **Workflow return values**: the workflow body has already returned, so nothing can catch it; the run fails. ```typescript lineNumbers async function stepWithBadArguments(value: unknown) { "use step"; return value; } export async function processWorkflow(someValue: unknown) { "use workflow"; try { await stepWithBadArguments(someValue); } catch (err) { // err.name === "SerializationError" // "Failed to serialize step arguments at path ..." } } ``` Uncaught, the error propagates out of the workflow body and the run fails immediately with the error code `USER_ERROR`; it does not retry. ## Why this happens Workflows persist their state using an event log. Every value that crosses execution boundaries must be: 1. **Serialized** to be stored in the event log 2. **Deserialized** when the workflow resumes Functions, class instances, symbols, and other non-serializable types cannot be properly reconstructed after serialization, which would break workflow replay. ## Common causes ### Passing functions {/* @skip-typecheck: incomplete code sample */} ```typescript lineNumbers // Error - functions cannot be serialized export async function processWorkflow() { "use workflow"; const callback = () => console.log("done"); // [!code highlight] await processStep(callback); // Error! // [!code highlight] } ``` **Solution:** Pass data instead, then define the function logic in the step. ```typescript lineNumbers // Fixed - pass configuration data instead export async function processWorkflow() { "use workflow"; await processStep({ shouldLog: true }); // [!code highlight] } async function processStep(config: { shouldLog: boolean }) { "use step"; if (config.shouldLog) { // [!code highlight] console.log("done"); // [!code highlight] } // [!code highlight] } ``` ### Class instances ```typescript lineNumbers class User { constructor(public name: string) {} greet() { return `Hello ${this.name}`; } } // Error - class instances lose methods after serialization export async function greetWorkflow() { "use workflow"; await greetStep(new User("Alice")); // Error! // [!code highlight] } ``` **Solution:** Pass plain objects and reconstruct the class in the step. ```typescript lineNumbers class User { constructor(public name: string) {} greet() { return `Hello ${this.name}`; } } // Fixed - pass plain object, reconstruct in step export async function greetWorkflow() { "use workflow"; await greetStep({ name: "Alice" }); // [!code highlight] } async function greetStep(userData: { name: string }) { "use step"; const user = new User(userData.name); // [!code highlight] console.log(user.greet()); } ``` ## Supported serializable types Workflow SDK supports these types across execution boundaries: ### Standard JSON types - `string`, `number`, `boolean`, `null` - Arrays of serializable values - Plain objects with serializable values To learn more about supported types, see the [Serialization](/docs/foundations/serialization) section. ## Debugging serialization issues To identify what's causing serialization to fail: 1. **Check the error stack trace**: It often shows which property failed. 2. **Simplify your data**: Temporarily pass smaller objects to isolate the issue. 3. **Use supported data types**: See [Serialization](/docs/foundations/serialization) for details.