Skip to content

workflow: serialize run and step errors - #304

Merged
fantix merged 1 commit into
mainfrom
fantix/serialized-errors
Aug 27, 2026
Merged

workflow: serialize run and step errors#304
fantix merged 1 commit into
mainfrom
fantix/serialized-errors

Conversation

@fantix

@fantix fantix commented Aug 15, 2026

Copy link
Copy Markdown
Member

This PR serializes errors thrown in workflows and steps so that such errors are persisted in the world and can be dehydrated later to the caller. New public APIs:

  • Awaiting on the return_value() of a failed workflow Run now raises a new vercel.workflow.WorkflowRunFailedError error instead of RuntimeError
  • Errors from steps are now passed to the caller workflow (known errors are passed transparently; user custom errors should use @serializable to offer hydrate/dehydrate functions, or they will become RemoteError)
  • Added vercel.workflow.RemoteError for unknown cross-language errors as a lossless wrapper (for e2e test)

Summary

  • Serialize errors in run_failed, step_failed, and step_retrying through the workflow payload codec.
  • Preserve error type, message, stack, and explicit cause chains across the event log.
  • Add the plaintext errorCode field used by the TypeScript workflow protocol.
  • Hydrate step failures back into workflow code and expose failed run details through WorkflowRunFailedError.

Motivation

The Python SDK still used the old {message, stack, code} error shape. Current TypeScript workflow packages expect serialized payloads, so TypeScript readers could lose Python failures and Python readers could receive raw serialized or encrypted data instead of an exception.

Implementation

Error wire handling now lives in error_serde.py. It uses the same dedicated tags as @workflow/core for shared error classes and a generic Error tag for other exceptions. RemoteError preserves TypeScript-only tags and fields, while @serializable remains the higher-priority path for custom exception classes.

StructuredError has been removed from run and step models. Error fields now retain their serialized payloads, and LocalWorld stores them without summarizing them. Encrypted error payloads resolve the run key before hydration.

For callers:

  • a failed step raises its hydrated exception at the workflow's await point;
  • exhausted retries wrap the last failure in FatalError and retain it as __cause__;
  • Run.return_value() raises WorkflowRunFailedError with the decoded failure as error/__cause__ and the plaintext classification as error_code.

Legacy Python string and {message, stack, code} records remain readable. JavaScript non-Error thrown values are represented as RuntimeError(value). Python control-flow exceptions such as KeyboardInterrupt continue to bypass workflow error serialization.

Verification

  • uv run poe qa vercel-workflow
  • uv run poe check-news-fragments
  • Python 3.10 matrix coverage for error serialization, step handling, workflow handling, and the end-to-end step-error integration test
  • End-to-end LocalWorld test proving a workflow can catch a step's FatalError with its TypeError cause intact

@fantix
fantix force-pushed the fantix/serialized-errors branch from b561e48 to 3f2844b Compare August 25, 2026 16:53
@fantix
fantix changed the base branch from main to fantix/set-attributes August 25, 2026 16:53
@fantix
fantix force-pushed the fantix/serialized-errors branch from 3f2844b to a7b021e Compare August 25, 2026 17:09
Base automatically changed from fantix/set-attributes to main August 26, 2026 18:40
@fantix
fantix force-pushed the fantix/serialized-errors branch from a7b021e to 7794142 Compare August 26, 2026 18:40
Preserve exception types, stacks, and causes across failed run and step
events. Expose the plaintext run error classification and hydrate recorded
failures for callers.
@fantix
fantix force-pushed the fantix/serialized-errors branch from 7794142 to ac679ca Compare August 26, 2026 20:19
@fantix fantix changed the title workflow: serialize a thrown value onto the error events workflow: serialize run and step errors Aug 26, 2026
@fantix
fantix marked this pull request as ready for review August 26, 2026 20:30
@fantix
fantix requested a review from msullivan August 26, 2026 20:31
@fantix
fantix merged commit 373f0ee into main Aug 27, 2026
14 checks passed
@fantix
fantix deleted the fantix/serialized-errors branch August 27, 2026 01:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants