workflow 5.0.0-beta.0 → 5.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +4 -4
  2. package/dist/api-workflow.d.ts +2 -4
  3. package/dist/api-workflow.d.ts.map +1 -1
  4. package/dist/api-workflow.js +3 -7
  5. package/dist/api.d.ts +5 -1
  6. package/dist/api.d.ts.map +1 -1
  7. package/dist/api.js +14 -2
  8. package/dist/astro.js +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/internal/builtins.d.ts +17 -0
  11. package/dist/internal/builtins.d.ts.map +1 -1
  12. package/dist/internal/builtins.js +65 -1
  13. package/dist/internal/class-serialization.js +1 -1
  14. package/dist/internal/errors.js +1 -1
  15. package/dist/nest.js +1 -1
  16. package/dist/next.cjs +4 -2
  17. package/dist/next.d.cts +1 -1
  18. package/dist/next.d.cts.map +1 -1
  19. package/dist/nitro.js +1 -1
  20. package/dist/nuxt.js +1 -1
  21. package/dist/observability.d.ts +2 -2
  22. package/dist/observability.js +3 -3
  23. package/dist/runtime.d.ts +1 -1
  24. package/dist/runtime.d.ts.map +1 -1
  25. package/dist/runtime.js +2 -2
  26. package/dist/stdlib.js +1 -1
  27. package/dist/sveltekit.js +1 -1
  28. package/dist/typescript-plugin.cjs +1 -1
  29. package/dist/vite.js +1 -1
  30. package/dist/workflow.js +1 -1
  31. package/docs/ai/index.mdx +6 -5
  32. package/docs/ai/resumable-streams.mdx +1 -1
  33. package/docs/api-reference/vitest/index.mdx +28 -1
  34. package/docs/api-reference/workflow/create-webhook.mdx +37 -18
  35. package/docs/api-reference/workflow/experimental-set-attributes.mdx +63 -0
  36. package/docs/api-reference/workflow/get-workflow-metadata.mdx +61 -0
  37. package/docs/api-reference/workflow/index.mdx +3 -0
  38. package/docs/api-reference/workflow-ai/durable-agent.mdx +0 -4
  39. package/docs/api-reference/workflow-ai/index.mdx +0 -5
  40. package/docs/api-reference/workflow-ai/workflow-chat-transport.mdx +0 -4
  41. package/docs/api-reference/workflow-api/get-world.mdx +6 -6
  42. package/docs/api-reference/workflow-api/index.mdx +1 -1
  43. package/docs/api-reference/workflow-api/start.mdx +5 -4
  44. package/docs/api-reference/workflow-api/world/index.mdx +2 -2
  45. package/docs/api-reference/workflow-api/world/observability.mdx +1 -1
  46. package/docs/api-reference/workflow-api/world/queue.mdx +1 -1
  47. package/docs/api-reference/workflow-api/world/storage.mdx +8 -8
  48. package/docs/api-reference/workflow-api/world/streams.mdx +38 -36
  49. package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +16 -6
  50. package/docs/api-reference/workflow-next/with-workflow.mdx +34 -2
  51. package/docs/changelog/attributes-mvp.mdx +368 -0
  52. package/docs/changelog/eager-processing.mdx +595 -0
  53. package/docs/changelog/index.mdx +2 -1
  54. package/docs/cookbook/advanced/child-workflows.mdx +313 -0
  55. package/docs/cookbook/advanced/meta.json +9 -0
  56. package/docs/cookbook/advanced/publishing-libraries.mdx +336 -0
  57. package/docs/cookbook/advanced/serializable-steps.mdx +147 -0
  58. package/docs/cookbook/advanced/upgrading-workflows.mdx +195 -0
  59. package/docs/cookbook/agent-patterns/agent-cancellation.mdx +156 -0
  60. package/docs/cookbook/agent-patterns/durable-agent.mdx +154 -0
  61. package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +255 -0
  62. package/docs/cookbook/agent-patterns/meta.json +4 -0
  63. package/docs/cookbook/common-patterns/batching.mdx +105 -0
  64. package/docs/cookbook/common-patterns/idempotency.mdx +107 -0
  65. package/docs/cookbook/common-patterns/meta.json +15 -0
  66. package/docs/cookbook/common-patterns/rate-limiting.mdx +228 -0
  67. package/docs/cookbook/common-patterns/saga.mdx +247 -0
  68. package/docs/cookbook/common-patterns/scheduling.mdx +129 -0
  69. package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +155 -0
  70. package/docs/cookbook/common-patterns/timeouts.mdx +99 -0
  71. package/docs/cookbook/common-patterns/webhooks.mdx +185 -0
  72. package/docs/cookbook/common-patterns/workflow-composition.mdx +111 -0
  73. package/docs/cookbook/index.mdx +38 -0
  74. package/docs/cookbook/integrations/ai-sdk.mdx +383 -0
  75. package/docs/cookbook/integrations/chat-sdk.mdx +307 -0
  76. package/docs/cookbook/integrations/meta.json +4 -0
  77. package/docs/cookbook/integrations/sandbox.mdx +520 -0
  78. package/docs/cookbook/meta.json +5 -0
  79. package/docs/deploying/building-a-world.mdx +46 -44
  80. package/docs/deploying/world/local-world.mdx +1 -1
  81. package/docs/deploying/world/postgres-world.mdx +15 -8
  82. package/docs/deploying/world/vercel-world.mdx +3 -1
  83. package/docs/errors/abort-signal-timeout-in-workflow.mdx +80 -0
  84. package/docs/errors/hook-conflict.mdx +56 -4
  85. package/docs/errors/index.mdx +3 -0
  86. package/docs/errors/runtime-decryption-failed.mdx +77 -0
  87. package/docs/errors/start-invalid-workflow-function.mdx +1 -1
  88. package/docs/foundations/cancellation.mdx +460 -0
  89. package/docs/foundations/errors-and-retries.mdx +7 -3
  90. package/docs/foundations/index.mdx +3 -3
  91. package/docs/foundations/meta.json +3 -2
  92. package/docs/foundations/serialization.mdx +78 -42
  93. package/docs/foundations/starting-workflows.mdx +6 -2
  94. package/docs/foundations/versioning.mdx +263 -0
  95. package/docs/getting-started/astro.mdx +6 -0
  96. package/docs/getting-started/index.mdx +13 -7
  97. package/docs/getting-started/meta.json +3 -1
  98. package/docs/getting-started/nestjs.mdx +8 -0
  99. package/docs/getting-started/next.mdx +29 -3
  100. package/docs/getting-started/nitro.mdx +22 -0
  101. package/docs/getting-started/python.mdx +165 -0
  102. package/docs/getting-started/sveltekit.mdx +6 -0
  103. package/docs/getting-started/tanstack-start.mdx +241 -0
  104. package/docs/how-it-works/cancellation.mdx +287 -0
  105. package/docs/how-it-works/code-transform.mdx +8 -7
  106. package/docs/how-it-works/event-sourcing.mdx +2 -2
  107. package/docs/how-it-works/meta.json +2 -1
  108. package/docs/internal/index.mdx +19 -0
  109. package/docs/internal/meta.json +5 -0
  110. package/docs/internal/serializable-abort-controller.mdx +148 -0
  111. package/docs/meta.json +1 -0
  112. package/docs/migration-guides/index.mdx +34 -0
  113. package/docs/migration-guides/meta.json +9 -0
  114. package/docs/migration-guides/migrating-from-aws-step-functions.mdx +358 -0
  115. package/docs/migration-guides/migrating-from-inngest.mdx +304 -0
  116. package/docs/migration-guides/migrating-from-temporal.mdx +313 -0
  117. package/docs/migration-guides/migrating-from-trigger-dev.mdx +328 -0
  118. package/docs/observability/attributes.mdx +66 -0
  119. package/docs/observability/index.mdx +9 -1
  120. package/docs/observability/meta.json +1 -1
  121. package/package.json +14 -14
  122. package/dist/internal/private.d.ts +0 -6
  123. package/dist/internal/private.d.ts.map +0 -1
  124. package/dist/internal/private.js +0 -6
  125. package/docs/foundations/common-patterns.mdx +0 -265
@@ -53,7 +53,8 @@ Create an `instrumentation.ts` file in your project root:
53
53
  export async function register() {
54
54
  if (process.env.NEXT_RUNTIME !== "edge") {
55
55
  const { getWorld } = await import("workflow/runtime");
56
- await getWorld().start?.();
56
+ const world = await getWorld();
57
+ await world.start?.();
57
58
  }
58
59
  }
59
60
  ```
@@ -73,7 +74,8 @@ import type { ServerInit } from "@sveltejs/kit";
73
74
 
74
75
  export const init: ServerInit = async () => {
75
76
  const { getWorld } = await import("workflow/runtime");
76
- await getWorld().start?.();
77
+ const world = await getWorld();
78
+ await world.start?.();
77
79
  };
78
80
  ```
79
81
 
@@ -92,7 +94,8 @@ import { defineNitroPlugin } from "nitro/~internal/runtime/plugin";
92
94
 
93
95
  export default defineNitroPlugin(async () => {
94
96
  const { getWorld } = await import("workflow/runtime");
95
- await getWorld().start?.();
97
+ const world = await getWorld();
98
+ await world.start?.();
96
99
  });
97
100
  ```
98
101
 
@@ -138,7 +141,7 @@ If `WORKFLOW_POSTGRES_URL` is not set, the CLI defaults to `postgres://world:wor
138
141
 
139
142
  Learn more in the [Observability](/docs/observability) documentation.
140
143
 
141
- ## Testing & Performance
144
+ ## Testing & Compatibility
142
145
 
143
146
  <WorldTestingPerformance />
144
147
 
@@ -158,7 +161,9 @@ Prefix for graphile-worker queue job names. Useful when sharing a database betwe
158
161
 
159
162
  ### `WORKFLOW_POSTGRES_WORKER_CONCURRENCY`
160
163
 
161
- Number of concurrent workers polling for jobs. Default: `10`
164
+ Number of concurrent workers polling for jobs. Default: `50`.
165
+
166
+ This value also bounds how many parent→child workflow polls can be in flight simultaneously. Every `await childRun.returnValue` inside a workflow holds a worker slot until the child run terminates — if you expect recursive or highly-fanned-out parent/child workflows, raise this ceiling above the peak number of concurrent polls. With the default of 50, the included `fibonacciWorkflow` e2e test (fib(6), ~24 concurrent polls at peak) passes; deeper recursion or larger fanouts need a correspondingly larger setting.
162
167
 
163
168
  ### `WORKFLOW_POSTGRES_MAX_POOL_SIZE`
164
169
 
@@ -168,15 +173,16 @@ For higher worker concurrency, Graphile Worker recommends setting `maxPoolSize`
168
173
 
169
174
  ### Programmatic configuration
170
175
 
171
- {/* @skip-typecheck: incomplete code sample */}
176
+ {/*@skip-typecheck: incomplete code sample*/}
177
+
172
178
  ```typescript title="workflow.config.ts" lineNumbers
173
179
  import { createWorld } from "@workflow/world-postgres";
174
180
 
175
181
  const world = createWorld({
176
182
  connectionString: "postgres://user:password@host:5432/database",
177
183
  jobPrefix: "myapp_",
178
- queueConcurrency: 20,
179
- maxPoolSize: 20, // overrides WORKFLOW_POSTGRES_MAX_POOL_SIZE
184
+ queueConcurrency: 50,
185
+ maxPoolSize: 52, // overrides WORKFLOW_POSTGRES_MAX_POOL_SIZE
180
186
  });
181
187
  ```
182
188
 
@@ -200,6 +206,7 @@ Deploy your application to any cloud that supports long-running servers:
200
206
  - Platform-as-a-Service providers (Railway, Render, Fly.io, etc.)
201
207
 
202
208
  Ensure your deployment has:
209
+
203
210
  1. Network access to your PostgreSQL database
204
211
  2. Environment variables configured correctly
205
212
  3. The `start()` function called on server initialization
@@ -83,7 +83,7 @@ npx workflow inspect runs \
83
83
 
84
84
  Learn more in the [Observability](/docs/observability) documentation.
85
85
 
86
- ## Testing & Performance
86
+ ## Testing & Compatibility
87
87
 
88
88
  <WorldTestingPerformance />
89
89
 
@@ -139,6 +139,8 @@ On Vercel, workflow runs are pegged to the deployment that started them. This me
139
139
 
140
140
  This ensures long-running workflows complete reliably without being affected by subsequent deployments.
141
141
 
142
+ For the full model, including rerunning on latest and explicit upgrade boundaries, see [Versioning](/docs/foundations/versioning).
143
+
142
144
  ## Security
143
145
 
144
146
  ### Consumer function security
@@ -0,0 +1,80 @@
1
+ ---
2
+ title: abort-signal-timeout-in-workflow
3
+ description: AbortSignal.timeout() cannot be used inside workflow functions because it relies on real timers which break deterministic replay.
4
+ type: troubleshooting
5
+ summary: Use sleep() with AbortController instead of AbortSignal.timeout() in workflow functions.
6
+ prerequisites:
7
+ - /docs/foundations/workflows-and-steps
8
+ related:
9
+ - /docs/foundations/cancellation
10
+ - /docs/api-reference/workflow/sleep
11
+ - /docs/errors/timeout-in-workflow
12
+ ---
13
+
14
+ ## Error
15
+
16
+ ```
17
+ AbortSignal.timeout() is not supported in workflow functions.
18
+ Use sleep() with an AbortController instead.
19
+ ```
20
+
21
+ ## Why This Happens
22
+
23
+ `AbortSignal.timeout()` creates a signal that aborts after a real-time delay using an internal timer. Workflow functions must be [deterministic](/docs/foundations/workflows-and-steps) to support replay — they run the same code multiple times during the workflow's lifecycle, using the [event log](/docs/how-it-works/event-sourcing) to resume execution to the correct point.
24
+
25
+ Real-time timers break this determinism because:
26
+ - On the first execution, the timer might fire after 10 seconds
27
+ - On replay, the timer would fire again, but the event log may have already advanced past that point
28
+ - The timer's behavior depends on wall-clock time, which varies between executions
29
+
30
+ ## How to Fix
31
+
32
+ Use [`sleep()`](/docs/api-reference/workflow/sleep) with an `AbortController` to create a deterministic timeout that cancels in-flight work:
33
+
34
+ **Before (incorrect):**
35
+
36
+ {/* @skip-typecheck: intentionally incorrect example */}
37
+ ```typescript lineNumbers
38
+ export async function workflow() {
39
+ "use workflow";
40
+
41
+ // This will throw an error
42
+ const signal = AbortSignal.timeout(10_000); // [!code highlight]
43
+ const result = await fetchData(signal);
44
+ return result;
45
+ }
46
+ ```
47
+
48
+ **After (correct):**
49
+
50
+ ```typescript lineNumbers
51
+ import { sleep } from "workflow";
52
+
53
+ export async function workflow() {
54
+ "use workflow";
55
+
56
+ const controller = new AbortController(); // [!code highlight]
57
+ void sleep("10s").then(() => controller.abort()); // [!code highlight]
58
+
59
+ return await fetchData(controller.signal);
60
+ }
61
+
62
+ async function fetchData(signal: AbortSignal) {
63
+ "use step";
64
+ const response = await fetch("https://api.example.com/data", { signal });
65
+ return response.json();
66
+ }
67
+ ```
68
+
69
+ The `sleep()` + `AbortController` pattern is the durable equivalent of `AbortSignal.timeout()`. The sleep is recorded in the event log, so it replays deterministically. If `fetchData` finishes within 10 seconds you get the response; if not, the timer fires `controller.abort()`, `fetch` rejects with an `AbortError`, and the step's failure propagates to the workflow as a `FatalError` (no retries — abort is intentional cancellation).
70
+
71
+ <Callout type="info">
72
+ `AbortSignal.timeout()` works normally inside step functions, since steps have full Node.js runtime access and are not replayed.
73
+ </Callout>
74
+
75
+ ## Related
76
+
77
+ - [Cancellation](/docs/foundations/cancellation) — Patterns for cancelling in-flight work
78
+ - [`sleep()` API Reference](/docs/api-reference/workflow/sleep) — Durable sleep primitive
79
+ - [Workflows and Steps](/docs/foundations/workflows-and-steps) — Why workflow functions must be deterministic
80
+ - [`setTimeout` in Workflow](/docs/errors/timeout-in-workflow) — Similar restriction on `setTimeout`
@@ -73,9 +73,9 @@ export async function processPayment() {
73
73
  }
74
74
  ```
75
75
 
76
- ## Handling Hook Conflicts in Your Workflow
76
+ ## Handling Hook Conflicts
77
77
 
78
- When a hook conflict occurs, awaiting the hook will throw a `HookConflictError`. You can catch this error to handle the conflict gracefully:
78
+ When a hook conflict occurs, awaiting the hook will throw a `HookConflictError`. The error exposes the token that conflicted and, for current worlds, the run ID that currently owns it. `conflictingRunId` remains optional for compatibility with older persisted events and world implementations, so guard it before delegating:
79
79
 
80
80
  ```typescript lineNumbers
81
81
  import { createHook } from "workflow";
@@ -93,14 +93,64 @@ export async function processPayment(orderId: string) {
93
93
  if (HookConflictError.is(error)) { // [!code highlight]
94
94
  // Another workflow is already processing this order
95
95
  console.log(`Conflicting token: ${error.token}`);
96
- return { success: false, reason: "duplicate-processing" };
96
+ if (error.conflictingRunId) {
97
+ console.log(`Active run: ${error.conflictingRunId}`);
98
+ }
99
+ return {
100
+ success: false,
101
+ reason: "duplicate-processing",
102
+ token: error.token,
103
+ runId: error.conflictingRunId
104
+ };
97
105
  }
98
106
  throw error; // Re-throw other errors
99
107
  }
100
108
  }
101
109
  ```
102
110
 
103
- This pattern is useful when you want to detect and handle duplicate processing attempts instead of letting the workflow fail.
111
+ This pattern is useful when you want to detect duplicate processing inside the workflow. Runtime APIs such as `resumeHook()` and `getRun()` must be called outside workflow functions, for example from an API route or in a step.
112
+
113
+ ### Delegate to the Active Run
114
+
115
+ In idempotency flows, a conflict means another active run already owns the hook token. You can return the duplicate-processing payload from the workflow, resume the active hook to deliver the payload to the existing run, then use `getRun(result.runId)` to wait for, stream, or cancel the active run:
116
+
117
+ ```typescript lineNumbers
118
+ import { getRun, resumeHook, start } from "workflow/api";
119
+ import { processPayment } from "@/workflows/process-payment";
120
+
121
+ type ProcessPaymentResult =
122
+ | { success: true; payment: unknown }
123
+ | {
124
+ success: false;
125
+ reason: "duplicate-processing";
126
+ token: string;
127
+ runId?: string;
128
+ };
129
+
130
+ export async function POST(request: Request) {
131
+ const { orderId, payment } = await request.json();
132
+ const run = await start(processPayment, [orderId]);
133
+ const result = (await run.returnValue) as ProcessPaymentResult;
134
+
135
+ if (
136
+ result.success === false &&
137
+ result.reason === "duplicate-processing" &&
138
+ result.runId
139
+ ) {
140
+ await resumeHook(result.token, payment); // [!code highlight]
141
+ const activeRun = getRun(result.runId); // [!code highlight]
142
+
143
+ return Response.json({
144
+ delegatedToRunId: activeRun.runId,
145
+ result: await activeRun.returnValue
146
+ });
147
+ }
148
+
149
+ return Response.json(result);
150
+ }
151
+ ```
152
+
153
+ If the caller needs live output instead of the final result, return `activeRun.getReadable()` from the same branch. If the duplicate request should replace the active work, call `await activeRun.cancel()` after inspecting the run.
104
154
 
105
155
  ## When Hook Tokens Are Released
106
156
 
@@ -122,4 +172,6 @@ After a workflow completes, its hook tokens become available for reuse by other
122
172
  ## Related
123
173
 
124
174
  - [Hooks](/docs/foundations/hooks) - Learn more about using hooks in workflows
175
+ - [getRun](/docs/api-reference/workflow-api/get-run) - Retrieve or control the active run
176
+ - [resumeHook](/docs/api-reference/workflow-api/resume-hook) - Deliver data to the active hook
125
177
  - [createWebhook](/docs/api-reference/workflow/create-webhook) - Alternative for fixed webhook URLs
@@ -43,6 +43,9 @@ Fix common mistakes when creating and executing workflows in the **Workflow SDK*
43
43
  <Card href="/docs/errors/workflow-not-registered" title="workflow-not-registered">
44
44
  Resolve workflow not registered errors caused by deployment mismatches.
45
45
  </Card>
46
+ <Card href="/docs/errors/runtime-decryption-failed" title="runtime-decryption-failed">
47
+ Resolve runtime decryption failures from the SDK's encryption layer.
48
+ </Card>
46
49
  </Cards>
47
50
 
48
51
  ## Learn More
@@ -0,0 +1,77 @@
1
+ ---
2
+ title: runtime-decryption-failed
3
+ description: The SDK's built-in AES-GCM encryption layer failed to encrypt or decrypt a workflow payload.
4
+ type: troubleshooting
5
+ summary: Resolve runtime decryption failures caused by ciphertext corruption, key mismatch, or malformed envelopes.
6
+ prerequisites:
7
+ - /docs/foundations/workflows-and-steps
8
+ related:
9
+ - /docs/foundations/errors-and-retries
10
+ ---
11
+
12
+ This error occurs when the Workflow SDK's built-in AES-GCM encryption layer fails while encrypting or decrypting a workflow payload. The SDK encrypts step inputs, step outputs, hook payloads, and other event-log data with a per-run AES-256 key whenever encryption is configured for the deployment.
13
+
14
+ This is an **internal SDK failure** — your workflow code never invokes the encryption primitives directly. When this surfaces, it means the ciphertext, nonce, or auth tag the SDK tried to verify is not the bytes that were originally produced. The run is failed with the `RUNTIME_ERROR` classification.
15
+
16
+ ## Error Message
17
+
18
+ ```
19
+ AES-256-GCM decryption failed: The operation failed for an operation-specific reason
20
+ ```
21
+
22
+ The underlying cause is a native Web Crypto [`OperationError`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException#operationerror) — most commonly raised by `AESCipherJob.onDone` in Node's `node:internal/crypto/util` module when the GCM authentication tag does not verify.
23
+
24
+ The thrown `RuntimeDecryptionError` carries a small `context` object with diagnostic fields to help triangulate the source:
25
+
26
+ - `operation` — `'encrypt'` or `'decrypt'`
27
+ - `byteLength` — total byte length of the payload at the failure site
28
+ - `formatPrefix` — the first 4 bytes of the input (`'encr'` for a well-formed encrypted envelope, otherwise a hex dump)
29
+
30
+ ## Why This Happens
31
+
32
+ Common causes, in rough order of likelihood:
33
+
34
+ 1. **Ciphertext mutation or truncation in transit.** The encrypted payload reached the SDK with bytes that differ from what storage holds. Possible sources include a truncated HTTP response from a workflow-server ref endpoint, an edge-cache miss returning a partial 200, or a proxy drop during streaming. A truncated body whose first 4 bytes happen to still spell `encr` produces the exact "auth tag mismatch" symptom.
35
+ 2. **Key resolution mismatch.** The key used to decrypt is not the key that was used to encrypt — e.g. the run's `deploymentId` was not threaded through key resolution and the SDK fell back to the wrong deployment's key material.
36
+ 3. **Malformed encrypted envelope.** The envelope is too short to contain the GCM nonce (12 bytes) and auth tag (16 bytes), so decryption is rejected before it begins.
37
+
38
+ ## What To Do
39
+
40
+ This error indicates an SDK or infrastructure problem — not a bug in your workflow code. Your workflow code does not need to change.
41
+
42
+ ### 1. Upgrade to the latest `workflow` package
43
+
44
+ The underlying issue may have already been identified and fixed:
45
+
46
+ ```bash
47
+ npm install workflow@latest
48
+ ```
49
+
50
+ ### 2. Retry the failed run
51
+
52
+ Since this is a fatal error, the run is automatically marked as `failed`. You can re-run it using the **Re-run** button in the Workflow Dashboard.
53
+
54
+ ### 3. Report the issue
55
+
56
+ If the error persists after upgrading, please [open an issue on GitHub](https://github.com/vercel/workflow/issues/new) so we can investigate. Include:
57
+
58
+ - The version of the `workflow` package you are using
59
+ - The run ID(s) of the affected workflow run(s)
60
+ - The full error message, including the `context` fields (`operation`, `byteLength`, `formatPrefix`)
61
+ - Whether the affected workflows make heavy use of large step inputs/outputs (which may indicate the failure is on the lazy-loaded ref read path)
62
+
63
+ ## This Error Cannot Be Caught
64
+
65
+ Like other `WorkflowRuntimeError` subclasses, a runtime decryption failure is **not catchable** inside your workflow function. The runtime cannot safely continue executing user code when an event-log payload can't be verified, so the entire run fails immediately and is marked as `failed`.
66
+
67
+ To handle this programmatically from outside the workflow, check the run status:
68
+
69
+ ```typescript lineNumbers
70
+ import { getRun } from "workflow/api";
71
+
72
+ const run = getRun("wrun_abc123");
73
+ const status = await run.status;
74
+ if (status === "failed") {
75
+ console.error("Run failed");
76
+ }
77
+ ```
@@ -123,7 +123,7 @@ Before calling `start()`:
123
123
  1. Confirm the function includes `"use workflow"` as its first statement.
124
124
  2. Confirm your framework integration is enabled (for Next.js, wrap `next.config.ts` with [`withWorkflow()`](/docs/api-reference/workflow-next/with-workflow)).
125
125
  3. Pass the imported workflow function directly to `start()`, not a wrapper callback.
126
- 4. Keep the function in a file that goes through Workflow DevKit's transform step.
126
+ 4. Keep the function in a file that goes through Workflow SDK's transform step.
127
127
 
128
128
  ## Related
129
129