workflow 5.0.0-beta.2 → 5.0.0-beta.20
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.
- package/dist/api-workflow.d.ts +1 -1
- package/dist/api-workflow.d.ts.map +1 -1
- package/dist/api-workflow.js +2 -2
- package/dist/api.d.ts +5 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +14 -2
- package/dist/internal/builtins.d.ts +17 -0
- package/dist/internal/builtins.d.ts.map +1 -1
- package/dist/internal/builtins.js +65 -1
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +2 -2
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +2 -2
- package/docs/ai/index.mdx +27 -23
- package/docs/api-reference/index.mdx +24 -0
- package/docs/api-reference/meta.json +8 -0
- package/docs/api-reference/vitest/index.mdx +28 -7
- package/docs/api-reference/workflow/create-hook.mdx +38 -0
- package/docs/api-reference/workflow/create-webhook.mdx +1 -0
- package/docs/api-reference/workflow/experimental-set-attributes.mdx +65 -0
- package/docs/api-reference/workflow/fetch.mdx +5 -0
- package/docs/api-reference/workflow/index.mdx +3 -0
- package/docs/api-reference/workflow-ai/durable-agent.mdx +7 -45
- package/docs/api-reference/workflow-api/get-hook-by-token.mdx +7 -0
- package/docs/api-reference/workflow-api/get-run.mdx +6 -0
- package/docs/api-reference/workflow-api/index.mdx +6 -8
- package/docs/api-reference/workflow-api/resume-hook.mdx +57 -0
- package/docs/api-reference/workflow-api/start.mdx +13 -5
- package/docs/api-reference/workflow-astro/index.mdx +18 -0
- package/docs/api-reference/workflow-astro/meta.json +4 -0
- package/docs/api-reference/workflow-astro/workflow.mdx +45 -0
- package/docs/api-reference/workflow-errors/hook-conflict-error.mdx +60 -0
- package/docs/api-reference/workflow-errors/index.mdx +85 -0
- package/docs/api-reference/workflow-errors/meta.json +5 -0
- package/docs/api-reference/workflow-errors/run-not-supported-error.mdx +58 -0
- package/docs/api-reference/workflow-errors/workflow-error.mdx +52 -0
- package/docs/api-reference/workflow-errors/workflow-run-failed-error.mdx +16 -6
- package/docs/api-reference/workflow-errors/workflow-run-not-completed-error.mdx +58 -0
- package/docs/api-reference/workflow-errors/workflow-runtime-error.mdx +58 -0
- package/docs/api-reference/workflow-nest/configure-workflow-controller.mdx +33 -0
- package/docs/api-reference/workflow-nest/index.mdx +31 -0
- package/docs/api-reference/workflow-nest/meta.json +9 -0
- package/docs/api-reference/workflow-nest/nest-local-builder.mdx +64 -0
- package/docs/api-reference/workflow-nest/workflow-controller.mdx +40 -0
- package/docs/api-reference/workflow-nest/workflow-module.mdx +74 -0
- package/docs/api-reference/workflow-next/with-workflow.mdx +34 -2
- package/docs/api-reference/workflow-nitro/index.mdx +59 -0
- package/docs/api-reference/workflow-nuxt/index.mdx +47 -0
- package/docs/api-reference/workflow-observability/hydrate-data.mdx +35 -0
- package/docs/api-reference/workflow-observability/hydrate-resource-io.mdx +62 -0
- package/docs/api-reference/workflow-observability/index.mdx +64 -0
- package/docs/api-reference/workflow-observability/meta.json +11 -0
- package/docs/api-reference/workflow-observability/observability-revivers.mdx +50 -0
- package/docs/api-reference/workflow-observability/parse-class-name.mdx +41 -0
- package/docs/api-reference/workflow-observability/parse-step-name.mdx +40 -0
- package/docs/api-reference/workflow-observability/parse-workflow-name.mdx +55 -0
- package/docs/api-reference/workflow-runtime/create-world.mdx +39 -0
- package/docs/api-reference/workflow-runtime/get-world-handlers.mdx +44 -0
- package/docs/api-reference/{workflow-api → workflow-runtime}/get-world.mdx +7 -10
- package/docs/api-reference/workflow-runtime/health-check.mdx +50 -0
- package/docs/api-reference/workflow-runtime/index.mdx +43 -0
- package/docs/api-reference/workflow-runtime/meta.json +12 -0
- package/docs/api-reference/workflow-runtime/set-world.mdx +49 -0
- package/docs/api-reference/workflow-runtime/workflow-entrypoint.mdx +42 -0
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/index.mdx +5 -8
- package/docs/api-reference/workflow-runtime/world/meta.json +4 -0
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/queue.mdx +2 -2
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/storage.mdx +11 -4
- package/docs/api-reference/{workflow-api → workflow-runtime}/world/streams.mdx +2 -2
- package/docs/api-reference/workflow-serde/index.mdx +0 -1
- package/docs/api-reference/workflow-serde/workflow-deserialize.mdx +1 -2
- package/docs/api-reference/workflow-serde/workflow-serialize.mdx +1 -2
- package/docs/api-reference/workflow-sveltekit/index.mdx +18 -0
- package/docs/api-reference/workflow-sveltekit/meta.json +4 -0
- package/docs/api-reference/workflow-sveltekit/workflow-plugin.mdx +42 -0
- package/docs/api-reference/workflow-vite/index.mdx +18 -0
- package/docs/api-reference/workflow-vite/meta.json +4 -0
- package/docs/api-reference/workflow-vite/workflow.mdx +48 -0
- package/docs/changelog/attributes-mvp.mdx +380 -0
- package/docs/changelog/eager-processing.mdx +269 -0
- package/docs/changelog/index.mdx +2 -1
- package/docs/changelog/lazy-event-creation.md +127 -0
- package/docs/changelog/meta.json +6 -1
- package/docs/changelog/resilient-start.mdx +31 -283
- package/docs/cookbook/advanced/child-workflows.mdx +315 -0
- package/docs/cookbook/advanced/meta.json +2 -3
- package/docs/cookbook/advanced/publishing-libraries.mdx +87 -29
- package/docs/cookbook/advanced/serializable-steps.mdx +17 -5
- package/docs/cookbook/advanced/upgrading-workflows.mdx +195 -0
- package/docs/cookbook/agent-patterns/agent-cancellation.mdx +156 -0
- package/docs/cookbook/agent-patterns/durable-agent.mdx +11 -184
- package/docs/cookbook/agent-patterns/human-in-the-loop.mdx +150 -173
- package/docs/cookbook/agent-patterns/meta.json +1 -7
- package/docs/cookbook/common-patterns/batching.mdx +44 -118
- package/docs/cookbook/common-patterns/idempotency.mdx +36 -52
- package/docs/cookbook/common-patterns/meta.json +4 -4
- package/docs/cookbook/common-patterns/rate-limiting.mdx +1 -1
- package/docs/cookbook/common-patterns/saga.mdx +128 -33
- package/docs/cookbook/common-patterns/scheduling.mdx +77 -193
- package/docs/cookbook/common-patterns/sequential-and-parallel.mdx +155 -0
- package/docs/cookbook/common-patterns/timeouts.mdx +100 -0
- package/docs/cookbook/common-patterns/workflow-composition.mdx +117 -0
- package/docs/cookbook/index.mdx +14 -17
- package/docs/cookbook/integrations/ai-sdk.mdx +330 -142
- package/docs/cookbook/integrations/chat-sdk.mdx +264 -151
- package/docs/cookbook/integrations/sandbox.mdx +482 -81
- package/docs/cookbook/meta.json +1 -1
- package/docs/deploying/building-a-world.mdx +1 -1
- package/docs/deploying/world/postgres-world.mdx +5 -3
- package/docs/deploying/world/vercel-world.mdx +2 -0
- package/docs/errors/abort-signal-timeout-in-workflow.mdx +80 -0
- package/docs/errors/corrupted-event-log.mdx +5 -5
- package/docs/errors/hook-conflict.mdx +56 -4
- package/docs/errors/index.mdx +9 -0
- package/docs/errors/replay-divergence.mdx +27 -0
- package/docs/errors/runtime-decryption-failed.mdx +77 -0
- package/docs/errors/step-executed-multiple-times.mdx +23 -0
- package/docs/errors/step-not-registered.mdx +1 -1
- package/docs/foundations/cancellation.mdx +459 -0
- package/docs/foundations/errors-and-retries.mdx +7 -3
- package/docs/foundations/hooks.mdx +29 -0
- package/docs/foundations/idempotency.mdx +236 -11
- package/docs/foundations/index.mdx +3 -3
- package/docs/foundations/meta.json +3 -2
- package/docs/foundations/serialization.mdx +78 -42
- package/docs/foundations/starting-workflows.mdx +6 -2
- package/docs/foundations/streaming.mdx +14 -23
- package/docs/foundations/versioning.mdx +263 -0
- package/docs/getting-started/astro.mdx +6 -0
- package/docs/getting-started/index.mdx +6 -7
- package/docs/getting-started/meta.json +1 -0
- package/docs/getting-started/nestjs.mdx +9 -0
- package/docs/getting-started/next.mdx +5 -3
- package/docs/getting-started/nitro.mdx +22 -0
- package/docs/getting-started/sveltekit.mdx +6 -0
- package/docs/getting-started/tanstack-start.mdx +241 -0
- package/docs/how-it-works/cancellation.mdx +287 -0
- package/docs/how-it-works/code-transform.mdx +2 -2
- package/docs/how-it-works/encryption.mdx +2 -2
- package/docs/how-it-works/event-sourcing.mdx +2 -2
- package/docs/how-it-works/meta.json +2 -1
- package/docs/internal/index.mdx +21 -0
- package/docs/internal/meta.json +10 -0
- package/docs/internal/nitro-native-build.mdx +38 -0
- package/docs/internal/nitro-web-ui.mdx +24 -0
- package/docs/internal/serializable-abort-controller.mdx +148 -0
- package/docs/migration-guides/migrating-from-aws-step-functions.mdx +63 -16
- package/docs/migration-guides/migrating-from-inngest.mdx +44 -22
- package/docs/migration-guides/migrating-from-temporal.mdx +43 -14
- package/docs/migration-guides/migrating-from-trigger-dev.mdx +59 -27
- package/docs/observability/attributes.mdx +87 -0
- package/docs/observability/index.mdx +25 -1
- package/docs/observability/meta.json +1 -1
- package/docs/observability/tracing.mdx +106 -0
- package/docs/testing/index.mdx +2 -2
- package/package.json +14 -13
- package/docs/api-reference/workflow-api/world/meta.json +0 -4
- package/docs/api-reference/workflow-api/world/observability.mdx +0 -164
- package/docs/cookbook/advanced/custom-serialization.mdx +0 -168
- package/docs/cookbook/advanced/durable-objects.mdx +0 -148
- package/docs/cookbook/advanced/isomorphic-packages.mdx +0 -145
- package/docs/cookbook/agent-patterns/stop-workflow.mdx +0 -216
- package/docs/cookbook/agent-patterns/tool-orchestration.mdx +0 -255
- package/docs/cookbook/agent-patterns/tool-streaming.mdx +0 -181
- package/docs/cookbook/common-patterns/child-workflows.mdx +0 -372
- package/docs/cookbook/common-patterns/content-router.mdx +0 -207
- package/docs/cookbook/common-patterns/fan-out.mdx +0 -208
- package/docs/foundations/common-patterns.mdx +0 -265
|
@@ -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`
|
|
@@ -9,21 +9,21 @@ related:
|
|
|
9
9
|
- /docs/foundations/errors-and-retries
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
This error occurs when the Workflow runtime
|
|
12
|
+
This error occurs when the Workflow runtime repeatedly cannot replay events in the event log. This usually means the event log is in an invalid state, such as duplicate or orphaned events, or that a runtime determinism bug persists across retry attempts.
|
|
13
13
|
|
|
14
|
-
This is a **workflow-level fatal error**. It cannot be caught or handled inside your workflow code.
|
|
14
|
+
This is a **workflow-level fatal error**. It cannot be caught or handled inside your workflow code. The runtime first retries transient replay divergence automatically; it marks the run as failed with this error only after replay still cannot recover.
|
|
15
15
|
|
|
16
16
|
## Error Message
|
|
17
17
|
|
|
18
18
|
```
|
|
19
|
-
|
|
19
|
+
Workflow replay diverged <divergenceCount> times after <maxRecoveryReplays> recovery replays; latest divergent event was <eventId>. Last divergence: <details>
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
## Why This Happens
|
|
23
23
|
|
|
24
24
|
Workflows persist their progress as an ordered event log. During replay, the runtime processes each event in sequence — every event must be consumed by a matching callback (e.g., a step or sleep waiting for its result). When an event has no matching consumer, the runtime cannot advance past it, which would block all subsequent events and hang the workflow indefinitely.
|
|
25
25
|
|
|
26
|
-
Instead of silently hanging, the runtime
|
|
26
|
+
Instead of silently hanging, the runtime retries a divergent replay before failing the workflow and surfacing this terminal error.
|
|
27
27
|
|
|
28
28
|
Common scenarios that produce this error:
|
|
29
29
|
|
|
@@ -45,7 +45,7 @@ npm install workflow@latest
|
|
|
45
45
|
|
|
46
46
|
### 2. Retry the failed run
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
If this error is displayed, automatic replay recovery has already been exhausted and the run has been marked as `failed`. You can re-run it using the **Re-run** button in the Workflow Dashboard.
|
|
49
49
|
|
|
50
50
|
### 3. Report the issue
|
|
51
51
|
|
|
@@ -73,9 +73,9 @@ export async function processPayment() {
|
|
|
73
73
|
}
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
-
## Handling Hook Conflicts
|
|
76
|
+
## Handling Hook Conflicts
|
|
77
77
|
|
|
78
|
-
When a hook conflict occurs, awaiting the hook will throw a `HookConflictError`.
|
|
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
|
-
|
|
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
|
|
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
|
package/docs/errors/index.mdx
CHANGED
|
@@ -37,12 +37,21 @@ Fix common mistakes when creating and executing workflows in the **Workflow SDK*
|
|
|
37
37
|
<Card href="/docs/errors/corrupted-event-log" title="corrupted-event-log">
|
|
38
38
|
Learn how to handle corrupted or invalid event logs.
|
|
39
39
|
</Card>
|
|
40
|
+
<Card href="/docs/errors/replay-divergence" title="replay-divergence">
|
|
41
|
+
Learn how workflow replay divergence is recovered automatically.
|
|
42
|
+
</Card>
|
|
40
43
|
<Card href="/docs/errors/step-not-registered" title="step-not-registered">
|
|
41
44
|
Resolve step not registered errors caused by deployment mismatches.
|
|
42
45
|
</Card>
|
|
46
|
+
<Card href="/docs/errors/step-executed-multiple-times" title="Step executed multiple times">
|
|
47
|
+
Diagnose duplicate step_started events from function crashes, timeouts, or OOMs.
|
|
48
|
+
</Card>
|
|
43
49
|
<Card href="/docs/errors/workflow-not-registered" title="workflow-not-registered">
|
|
44
50
|
Resolve workflow not registered errors caused by deployment mismatches.
|
|
45
51
|
</Card>
|
|
52
|
+
<Card href="/docs/errors/runtime-decryption-failed" title="runtime-decryption-failed">
|
|
53
|
+
Resolve runtime decryption failures from the SDK's encryption layer.
|
|
54
|
+
</Card>
|
|
46
55
|
</Cards>
|
|
47
56
|
|
|
48
57
|
## Learn More
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: replay-divergence
|
|
3
|
+
description: A workflow replay temporarily followed a path that did not match its recorded events.
|
|
4
|
+
type: troubleshooting
|
|
5
|
+
summary: Understand automatic recovery when a workflow replay diverges from its event history.
|
|
6
|
+
prerequisites:
|
|
7
|
+
- /docs/foundations/workflows-and-steps
|
|
8
|
+
related:
|
|
9
|
+
- /docs/errors/corrupted-event-log
|
|
10
|
+
- /docs/foundations/errors-and-retries
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
A replay divergence occurs when one invocation of a workflow cannot consume the durable event history using the promises, hooks, sleeps, or steps it created during replay.
|
|
14
|
+
|
|
15
|
+
This is an SDK/runtime signal, not an error thrown by your workflow code. It is not catchable inside a workflow function.
|
|
16
|
+
|
|
17
|
+
## Automatic Recovery
|
|
18
|
+
|
|
19
|
+
A single divergent replay does not prove that persisted history is corrupted. For example, asynchronous delivery ordering may cause one invocation to follow the wrong side of a race while another replay can follow the recorded history correctly.
|
|
20
|
+
|
|
21
|
+
The runtime automatically queues another replay when an invocation reports `REPLAY_DIVERGENCE`. No terminal `run_failed` event is written during these recovery attempts.
|
|
22
|
+
|
|
23
|
+
If recovery replays continue to diverge after the retry budget is exhausted, the runtime marks the run as failed with `CORRUPTED_EVENT_LOG` and records the latest divergent event for diagnosis.
|
|
24
|
+
|
|
25
|
+
## What To Do
|
|
26
|
+
|
|
27
|
+
Most replay divergence signals recover without action. If a run ultimately fails with `CORRUPTED_EVENT_LOG`, update to the latest `workflow` package and report the run ID and error details if the failure persists.
|
|
@@ -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
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Step executed multiple times
|
|
3
|
+
description: A step ran more than once because its function invocation crashed before it could report a result.
|
|
4
|
+
type: troubleshooting
|
|
5
|
+
summary: Diagnose duplicate step_started events caused by function timeouts, OOMs, or network issues.
|
|
6
|
+
prerequisites:
|
|
7
|
+
- /docs/foundations/workflows-and-steps
|
|
8
|
+
related:
|
|
9
|
+
- /docs/observability
|
|
10
|
+
- /docs/foundations/errors-and-retries
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
There may be cases where you see multiple `step_started` events for the same step in a workflow run. This happens if the function invocation executing the step crashes unexpectedly, and the step can not report the error. The step will be re-tried according to your retry policy in this case, but no error will be visible in the [Observability UI](/docs/observability).
|
|
14
|
+
|
|
15
|
+
## Common Causes
|
|
16
|
+
|
|
17
|
+
- **Function timeouts**: if your step code runs longer than the configured maximum function duration, it will be killed. Compare the gap between the `step_started` events to your configured function duration to be sure.
|
|
18
|
+
- **Out of memory (OOM)**: if your step code loads enough data into memory, especially if the step is invoked concurrently, the function invocation might run out of memory. You can see your function's peak memory use by going to the [Observability Query page](https://vercel.com/docs/observability) and showing the **Function Invocation Peak Memory** metric, then filtering down the **Route** to `/.well-known/workflow` endpoints.
|
|
19
|
+
- **Network issues**: persistent firewall, network stability, and related issues might prevent your function from reporting results or errors. This should be temporary.
|
|
20
|
+
|
|
21
|
+
## Getting Help
|
|
22
|
+
|
|
23
|
+
If you consistently see multiple `step_started` events and have ruled out function timeouts, OOMs, and firewall issues, please [contact support](https://vercel.com/help).
|
|
@@ -4,7 +4,7 @@ description: A step function is not registered in the current deployment.
|
|
|
4
4
|
type: troubleshooting
|
|
5
5
|
summary: Resolve step not registered errors caused by build issues.
|
|
6
6
|
prerequisites:
|
|
7
|
-
- /docs/foundations/steps
|
|
7
|
+
- /docs/foundations/workflows-and-steps
|
|
8
8
|
related:
|
|
9
9
|
- /docs/errors/workflow-not-registered
|
|
10
10
|
- /docs/api-reference/workflow-errors/step-not-registered-error
|