workflow 5.0.0-beta.44 → 5.0.0-beta.47
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/docs/api-reference/workflow-api/resume-hook.mdx +5 -3
- package/docs/api-reference/workflow-api/resume-webhook.mdx +1 -1
- package/docs/api-reference/workflow-api/start.mdx +12 -0
- package/docs/changelog/attributes-mvp.mdx +2 -2
- package/docs/changelog/index.mdx +1 -0
- package/docs/changelog/lazy-hook-resume.mdx +78 -0
- package/docs/changelog/meta.json +1 -0
- package/docs/changelog/resilient-resume.mdx +11 -1
- package/docs/configuration/runtime-tuning.mdx +2 -9
- package/docs/errors/corrupted-event-log.mdx +16 -7
- package/docs/foundations/errors-and-retries.mdx +1 -1
- package/docs/whats-new.mdx +1 -1
- package/package.json +12 -12
|
@@ -12,9 +12,11 @@ related:
|
|
|
12
12
|
|
|
13
13
|
Resumes a workflow run by sending a payload to a hook identified by its token.
|
|
14
14
|
|
|
15
|
-
It
|
|
15
|
+
It durably writes the `hook_received` event and only then publishes a workflow wake. The call resolves only after both operations succeed, in that order.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
`resumeHook()` throws `HookNotFoundError` when no hook holds the token or when its `hook_received` write is refused because the hook was disposed or the run ended. See [durable hook resume](/docs/changelog/lazy-hook-resume).
|
|
18
|
+
|
|
19
|
+
If `resumeHook()` throws any other error, the outcome is ambiguous only in dispatch, never in durability: the event may already be durable even though the workflow wake failed, and any later wake of the run delivers it. Calling `resumeHook()` again creates a new `resumeId` and can append a second `hook_received`. Callers that need at-most-once behavior across separate invocations must retain and deduplicate their own request key.
|
|
18
20
|
|
|
19
21
|
<Callout type="warn">
|
|
20
22
|
`resumeHook` is a runtime function that must be called from outside a workflow function.
|
|
@@ -50,7 +52,7 @@ showSections={["parameters"]}
|
|
|
50
52
|
|
|
51
53
|
### Returns
|
|
52
54
|
|
|
53
|
-
Returns a `Promise<ResumedHook>`, a `Hook` extended with an optional `resilientResume` flag. Resolving means the
|
|
55
|
+
Returns a `Promise<ResumedHook>`, a `Hook` extended with an optional `resilientResume` flag. Resolving means the payload is durably recorded as `hook_received` and the workflow wake was accepted. `resilientResume` is retained for source compatibility and is no longer set by any path. The resolved hook:
|
|
54
56
|
|
|
55
57
|
<TSDoc
|
|
56
58
|
definition={`
|
|
@@ -11,7 +11,7 @@ related:
|
|
|
11
11
|
|
|
12
12
|
Resumes a workflow run by sending an HTTP `Request` to a webhook identified by its token.
|
|
13
13
|
|
|
14
|
-
This function
|
|
14
|
+
This function publishes a workflow invocation carrying the request; the runtime creates the `hook_received` event from it and continues execution. It's designed to be called from API routes or server actions that receive external HTTP requests.
|
|
15
15
|
|
|
16
16
|
<Callout type="warn">
|
|
17
17
|
`resumeWebhook` is a runtime function that must be called from outside a workflow function.
|
|
@@ -108,6 +108,18 @@ The `deploymentId` option is currently a Vercel-specific feature. Other Worlds m
|
|
|
108
108
|
In Worlds without atomic, immutable deployments (such as local development or self-hosted Postgres), there is no notion of multiple deployments to resolve between, so `deploymentId: "latest"` has no effect: the SDK logs a warning and the run targets the current deployment. This means a workflow that opts into `"latest"` on Vercel still runs unchanged in local development.
|
|
109
109
|
</Callout>
|
|
110
110
|
|
|
111
|
+
<Callout type="info">
|
|
112
|
+
Resolving `"latest"` is the one `start()` path that calls the Vercel API, so it
|
|
113
|
+
needs an identity that can see the calling deployment. Inside a Vercel
|
|
114
|
+
deployment the SDK authenticates with the deployment's own OIDC token, which
|
|
115
|
+
carries the owning team, and this takes precedence over a `VERCEL_TOKEN` set in
|
|
116
|
+
the function's environment. A `VERCEL_TOKEN` belongs to a *user* and carries no
|
|
117
|
+
team, so authenticating with it scopes the lookup to that user's default team
|
|
118
|
+
and fails with a 404 whenever that is not the team that owns the deployment.
|
|
119
|
+
Outside a deployment (CLI, CI, the dashboard) `VERCEL_TOKEN` is still used;
|
|
120
|
+
configure the World's `teamId` so the request is scoped explicitly.
|
|
121
|
+
</Callout>
|
|
122
|
+
|
|
111
123
|
<Callout type="warn">
|
|
112
124
|
When using `deploymentId: "latest"`, the workflow run will execute on a potentially different deployment than the one calling `start()`. Be mindful of forward and backward compatibility:
|
|
113
125
|
|
|
@@ -273,7 +273,7 @@ If you need behavior the MVP does not provide (read, list, filter, initial attri
|
|
|
273
273
|
Unit tests in `@workflow/world` (validation surface) and `@workflow/core` (VM-side dispatch + host-side stub):
|
|
274
274
|
|
|
275
275
|
- Validation rules: key length, value byte cap, `$` prefix, per-batch duplicates, post-merge count cap (with `existingKeys` so updates of present keys don't falsely trip the cap)
|
|
276
|
-
- Reserved `$` namespace: rejected by default
|
|
276
|
+
- Reserved `$` namespace: rejected by default and accepted by the contextual `validateAttributeChanges` check when `allowReservedAttributes: true` is passed
|
|
277
277
|
- `experimental_setAttributes({})` is a no-op (no dispatch, no events)
|
|
278
278
|
- `undefined` value normalizes to a `null`-valued change on the wire
|
|
279
279
|
- The `{ allowReservedAttributes: true }` opt-in is forwarded through the step bridge so the world receives the flag
|
|
@@ -367,7 +367,7 @@ For the MVP the endpoint reuses the existing `WORKFLOW_EVENT` fact with `eventTy
|
|
|
367
367
|
|
|
368
368
|
### Validation rules are shared between SDK and world
|
|
369
369
|
|
|
370
|
-
|
|
370
|
+
Context-free validation lives in the exported Zod schemas (`AttributeKeySchema`, `AttributeValueSchema`, `AttributeChangeSchema`, and `AttributeChangesSchema`). The schema-free `validateAttributeChanges` helper adds rules that depend on caller context, including the post-merge count and reserved `$` namespace. Both the SDK `experimental_setAttributes` helper and the `world-local` / `world-postgres` implementations call it; the `world-vercel` backing service applies the same rules independently. The shared module remains the authoritative spec for the limits (256-char keys, 256-byte values, max 64 attributes per run, `$`-prefixed keys reserved).
|
|
371
371
|
|
|
372
372
|
### Run row reconstruction had to thread `attributes` through
|
|
373
373
|
|
package/docs/changelog/index.mdx
CHANGED
|
@@ -12,6 +12,7 @@ Stay up to date with the latest changes to Workflow SDK.
|
|
|
12
12
|
|
|
13
13
|
## 2026
|
|
14
14
|
|
|
15
|
+
- [Durable hook resume](/docs/changelog/lazy-hook-resume) (August 2026)
|
|
15
16
|
- [Resilient hook resume](/docs/changelog/resilient-resume) (July 2026)
|
|
16
17
|
- [Eager processing of steps and incremental event replay](/docs/changelog/eager-processing) (March 2026)
|
|
17
18
|
- Serializable AbortController and AbortSignal (March 12, 2026)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Durable hook resume
|
|
3
|
+
description: resumeHook() durably writes hook_received and only then publishes the workflow wake, so a resolved call can never be lost to a disposal race.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Durable hook resume
|
|
7
|
+
|
|
8
|
+
## Motivation
|
|
9
|
+
|
|
10
|
+
The previous lazy path published the serialized hook payload on the workflow
|
|
11
|
+
queue and left the queue consumer to create `hook_received`. If the hook was
|
|
12
|
+
disposed after `resumeHook()` returned but before the consumer write committed,
|
|
13
|
+
that write was rejected and the acknowledged queue delivery could not resume the
|
|
14
|
+
workflow: the caller was told the resume succeeded, and it was lost.
|
|
15
|
+
|
|
16
|
+
`resumeHook()` now resolves only after both the durable event write and the
|
|
17
|
+
workflow wake have succeeded, in that order.
|
|
18
|
+
|
|
19
|
+
## Design
|
|
20
|
+
|
|
21
|
+
The dispatch is strictly serial:
|
|
22
|
+
|
|
23
|
+
1. The hook is resolved by token. An unknown token throws `HookNotFoundError`.
|
|
24
|
+
2. The producer writes `hook_received` durably into the run's event log. A
|
|
25
|
+
client-minted `resumeId` and payload digest ride the write when the backend
|
|
26
|
+
supports atomic resume claims, so transport-level retries of the same write
|
|
27
|
+
converge on exactly one committed event. A write refused because the hook
|
|
28
|
+
was disposed or the run ended throws `HookNotFoundError`.
|
|
29
|
+
3. Only after the write is acknowledged does the producer publish the workflow
|
|
30
|
+
wake. The wake carries no payload — the payload lives in the event log — so
|
|
31
|
+
nothing rides on the queue message but the trigger. Publication is retried
|
|
32
|
+
a bounded number of times.
|
|
33
|
+
|
|
34
|
+
Because the event is committed before the wake exists, a disposal or run
|
|
35
|
+
completion racing the queue delivery cannot erase a resume the caller was told
|
|
36
|
+
succeeded: the delivery replays the committed event from the log.
|
|
37
|
+
|
|
38
|
+
- `ResumedHook.resilientResume` remains on the type for source compatibility
|
|
39
|
+
and is no longer set. The internal `resumeHookDurable()` entry point is
|
|
40
|
+
removed; `resumeHook()` itself now provides the durable guarantee.
|
|
41
|
+
|
|
42
|
+
A resolved call proves that the event is durable and the wake was accepted.
|
|
43
|
+
`HookNotFoundError` proves this invocation committed no event. Any other thrown
|
|
44
|
+
error is ambiguous only in *dispatch*, never in durability: a wake failure
|
|
45
|
+
after the write leaves the event committed, and any later wake of the run
|
|
46
|
+
(from any source) delivers it. A fresh `resumeHook()` invocation mints a new
|
|
47
|
+
`resumeId`, so blindly retrying a failed call can append a second
|
|
48
|
+
`hook_received`; callers that need at-most-once behavior across separate
|
|
49
|
+
invocations must deduplicate on their own request key.
|
|
50
|
+
|
|
51
|
+
## Behavior change: resumes against an ended run
|
|
52
|
+
|
|
53
|
+
The lazy path never observed the server's rejection — it published a message
|
|
54
|
+
and resolved, so a resume against a run that had already ended reported
|
|
55
|
+
success (reachable whenever the hook record outlives its run, e.g. token
|
|
56
|
+
retention). The durable write restores the check: **a resume against an ended
|
|
57
|
+
run now throws `HookNotFoundError`**, and a late webhook delivery to a
|
|
58
|
+
finished run answers 404 where it previously answered 202. Senders that treat
|
|
59
|
+
4xx as terminal will stop retrying such deliveries; that is the correct
|
|
60
|
+
signal, since nothing can resume an ended run.
|
|
61
|
+
|
|
62
|
+
A transient write conflict (HTTP 409, e.g. an event-slot conflict that
|
|
63
|
+
escaped the server's internal retry budget under contention) is no longer
|
|
64
|
+
re-keyed to `HookNotFoundError`. It surfaces as a retryable error, and its
|
|
65
|
+
rejected transaction committed nothing, so retrying the resume is safe.
|
|
66
|
+
|
|
67
|
+
## Compatibility
|
|
68
|
+
|
|
69
|
+
Nothing about the queue message changes: the wake has the same shape the
|
|
70
|
+
sequential path always published, so no consumer, backend, or server
|
|
71
|
+
coordination is needed and either side can roll back independently.
|
|
72
|
+
|
|
73
|
+
Consumers continue to accept legacy `hookInput` messages from older producers,
|
|
74
|
+
materializing their payload before replay. This permits rolling upgrades
|
|
75
|
+
without a coordinated producer and consumer deployment.
|
|
76
|
+
|
|
77
|
+
`WORKFLOW_DISABLE_LAZY_HOOK_RESUME` no longer gates anything and is ignored:
|
|
78
|
+
there is no lazy path left to disable.
|
package/docs/changelog/meta.json
CHANGED
|
@@ -5,6 +5,16 @@ description: resumeHook() now tolerates transient event storage failures when th
|
|
|
5
5
|
|
|
6
6
|
# Resilient `resumeHook()`
|
|
7
7
|
|
|
8
|
+
<Callout type="info">
|
|
9
|
+
Superseded by [durable hook resume](/docs/changelog/lazy-hook-resume):
|
|
10
|
+
`resumeHook()` now writes the `hook_received` event durably and only then
|
|
11
|
+
publishes the workflow wake, so the two-writer design, the queue-carried
|
|
12
|
+
payload, and the `resilientResume` flag described below are historical. The
|
|
13
|
+
`(runId, resumeId)` constraint remains, converging transport-level retries
|
|
14
|
+
of the producer's own write (and legacy `hookInput` redeliveries from older
|
|
15
|
+
producers).
|
|
16
|
+
</Callout>
|
|
17
|
+
|
|
8
18
|
## Motivation
|
|
9
19
|
|
|
10
20
|
`resumeHook()` used to write the `hook_received` event and dispatch the workflow queue message strictly one after the other, so every resume paid two sequential round trips and a transient event-storage failure failed the whole resume even when the queue was healthy. This change runs both writes **concurrently** (cutting a round trip off resume latency) and, on the same path, brings `resumeHook()` to parity with [resilient `start()`](/docs/changelog/resilient-start): a transient event-write failure no longer fails the resume when the payload can still be delivered through the queue.
|
|
@@ -19,4 +29,4 @@ description: resumeHook() now tolerates transient event storage failures when th
|
|
|
19
29
|
|
|
20
30
|
## Compatibility
|
|
21
31
|
|
|
22
|
-
The parallel fast path is gated per resume: it activates only when both the target run's queue consumer and the live backend independently attest dedup support (re-checked on every resume, so rollout and rollback both degrade safely). Otherwise (for oversized payloads
|
|
32
|
+
The parallel fast path is gated per resume: it activates only when both the target run's queue consumer and the live backend independently attest dedup support (re-checked on every resume, so rollout and rollback both degrade safely). Otherwise (for oversized payloads or legacy runs), `resumeHook()` falls back to the original sequential write-then-dispatch path. Because runs keep executing on the deployment they were created on, a resume targeting a run from an older deployment uses the sequential path.
|
|
@@ -81,13 +81,6 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
|
|
|
81
81
|
- Default: `3`
|
|
82
82
|
- Recovery replays before replay divergence is recorded as corruption.
|
|
83
83
|
|
|
84
|
-
### `WORKFLOW_DISABLE_LAZY_HOOK_RESUME`
|
|
85
|
-
|
|
86
|
-
- Default: enabled (lazy hook resume on)
|
|
87
|
-
- Resuming a hook persists the `hook_received` event and publishes the workflow invocation concurrently, cutting a round trip off resume latency. On this parallel path, the queue message also carries the payload, so a transient event-write failure still resumes the run. The queue consumer re-ensures the `hook_received` event before replay. A backend `(runId, resumeId)` constraint keeps the two writers converging on exactly one event.
|
|
88
|
-
- The runtime falls back to the sequential path when the consumer or backend does not attest dedup support (or the payload is too large to inline on the queue message). On the sequential path, the event is written *before* dispatch, and its failure fails the resume. The fallback trades away that resilience to stay safe when dedup is not enforced; it does not preserve it.
|
|
89
|
-
- Set `1` to force the sequential path as a kill switch. The chosen strategy is reported on the resume span as `workflow.hook.resume_strategy`.
|
|
90
|
-
|
|
91
84
|
### `WORKFLOW_DEPLOYMENT_MISMATCH_MAX_RETRIES`
|
|
92
85
|
|
|
93
86
|
- Default: `3`
|
|
@@ -99,7 +92,7 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
|
|
|
99
92
|
### `WORKFLOW_RESILIENT_STEP_DISPATCH`
|
|
100
93
|
|
|
101
94
|
- Default: disabled
|
|
102
|
-
- When a suspension hands newly created steps to the queue, the runtime publishes each step's execution message in parallel with its `step_created` event write instead of sequencing them, cutting a round trip per dispatched step. The message also carries the serialized step input (`stepInput`), so a transient `step_created` write failure (429 / 5xx / transport) still executes the step. The queue consumer idempotently re-ensures the event before running it, converging with the producer's write on the step's correlation ID. This mirrors resilient start (`runInput`) and the lazy hook resume
|
|
95
|
+
- When a suspension hands newly created steps to the queue, the runtime publishes each step's execution message in parallel with its `step_created` event write instead of sequencing them, cutting a round trip per dispatched step. The message also carries the serialized step input (`stepInput`), so a transient `step_created` write failure (429 / 5xx / transport) still executes the step. The queue consumer idempotently re-ensures the event before running it, converging with the producer's write on the step's correlation ID. This mirrors resilient start (`runInput`) and the legacy lazy hook resume's `hookInput` (which current producers no longer send; see [durable hook resume](/docs/changelog/lazy-hook-resume)).
|
|
103
96
|
- It is off by default because the publish races the create's verdict, and a create can come back refused: as a duplicate this replay should stop pursuing, or as a [stale write](#stale-reads-and-why-nothing-has-to-be-rejected) on a World that refuses rather than reports. Either way the message carrying the payload is already out, so the consumer can materialize a step whose create was refused, and nothing orders the verdict before the consumer's redelivery re-ensure. The sequential path is the only one that gives the message a happens-after edge over it.
|
|
104
97
|
- Even when enabled, the runtime falls back to the sequential create-then-publish dispatch when the step input is too large to inline on the queue message, or when the run's queue transport cannot carry binary payloads (pre-CBOR spec versions).
|
|
105
98
|
- Producer-side recoveries are reported on the suspension span as `workflow.step.resilient_dispatch_recovered`; a consumer that materialized the event reports `workflow.step.resilient_dispatch_materialized`.
|
|
@@ -193,7 +186,7 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
|
|
|
193
186
|
|
|
194
187
|
- Default: enabled
|
|
195
188
|
- Keeps the suspended workflow VM alive across inline steps within one invocation, so each iteration of the inline loop appends only the newly written events instead of replaying the whole event log in a fresh VM.
|
|
196
|
-
- Suspensions involving
|
|
189
|
+
- A step-driven suspension can keep the VM retained even when hooks are open or created at the same boundary. Hook-only suspensions park the invocation. Suspensions involving waits or attributes, runs with an open wait, and any replay divergence fall back to a full replay.
|
|
197
190
|
- Step inputs made of plain data (objects, arrays, primitives) and standard built-ins (`Map`, `Set`, `Date`, `RegExp`, typed arrays, `ArrayBuffer`, `URL`, `Headers`) keep the VM retained. Patching or polyfilling built-in prototypes doesn't change that because serialization never calls them. A boundary falls back to a full replay only when serializing its arguments runs code the workflow controls, such as a getter, a proxy, or a custom class serializer, or computes an `Error`'s stack trace.
|
|
198
191
|
- Set `0` or `false` to replay from scratch in a fresh VM on every iteration.
|
|
199
192
|
|
|
@@ -1,37 +1,46 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: corrupted-event-log
|
|
3
|
-
description: The workflow's event log contains an event that
|
|
3
|
+
description: The workflow's event log contains an event that cannot be processed or a stored payload that cannot be read.
|
|
4
4
|
type: troubleshooting
|
|
5
|
-
summary: Resolve corrupted event log errors caused by
|
|
5
|
+
summary: Resolve corrupted event log errors caused by invalid events or unreadable stored payloads.
|
|
6
6
|
prerequisites:
|
|
7
7
|
- /docs/foundations/workflows-and-steps
|
|
8
8
|
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 cannot safely replay the event log. The log may be in an invalid state, such as an orphaned event or one no consumer can attribute to anything the workflow did, or it may reference a stored payload that the World can no longer read.
|
|
13
13
|
|
|
14
|
-
This is a **workflow-level fatal error**. It cannot be caught or handled inside your workflow code. The runtime
|
|
14
|
+
This is a **workflow-level fatal error**. It cannot be caught or handled inside your workflow code. The runtime retries transient replay divergence automatically, but an unreadable stored payload is terminal immediately because replaying cannot restore it.
|
|
15
15
|
|
|
16
16
|
## Error message
|
|
17
17
|
|
|
18
|
+
For replay divergence:
|
|
19
|
+
|
|
18
20
|
```text
|
|
19
21
|
Workflow replay diverged <divergenceCount> times after <maxRecoveryReplays> recovery replays; latest divergent event was <eventId>. Last divergence: <details>
|
|
20
22
|
```
|
|
21
23
|
|
|
24
|
+
For an unreadable stored payload:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
the event log references a payload that no longer exists in storage: <details>
|
|
28
|
+
```
|
|
29
|
+
|
|
22
30
|
## Why this happens
|
|
23
31
|
|
|
24
32
|
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, such as a step or sleep waiting for its result. An event no callback ever claims is one the runtime would have to drop to finish the run, so it fails the run instead of returning a result that silently ignored it.
|
|
25
33
|
|
|
26
34
|
A delivery written from outside the replay, such as a hook firing or a step completing on another invocation, can land ahead of the events the replay is writing itself. That is ordinary concurrency rather than corruption, so the runtime holds such an event and offers it to each consumer the replay registers afterwards. The failure comes only when the workflow function returns while an event is still held, at which point no consumer can ever appear. A replay that suspends still holding one reports it on the span (`workflow.events.parked.count`, `.event_id`, `.event_type`) and leaves the decision to the replay that follows.
|
|
27
35
|
|
|
28
|
-
Before failing, the runtime retries
|
|
36
|
+
Before failing on divergence, the runtime retries the replay and surfaces this terminal error only if replay still cannot recover. It does not retry a payload that the World reports as permanently missing.
|
|
29
37
|
|
|
30
38
|
Common scenarios that produce this error:
|
|
31
39
|
|
|
32
40
|
- **An unclaimed event that repeats nothing**: A duplicate of a kind the log already records for that entity is read past rather than failing the run, so a second `step_completed` or `wait_completed` is not this error (see [Duplicate Events](/docs/how-it-works/event-sourcing#duplicate-events)). What fails is an unclaimed event with no earlier counterpart to defer to: a `step_started` behind a `step_completed` on a log that never recorded a `step_started`, for instance. No consumer remains for the step, and there is no earlier event of that kind the replay could be reading instead.
|
|
33
41
|
- **Orphaned events**: A `step_completed` or `wait_completed` event whose `correlationId` doesn't match any step or sleep in the workflow code, so the replay reaches its end still holding it.
|
|
34
42
|
- **A hole in the log**: Events are numbered by their position in the run's log, and those positions are dense, so a position below the log's highest that holds no event means the log the replay loaded is incomplete. The runtime cannot tell a position no write ever occupied from one whose event it failed to read, so it refuses to replay rather than produce a result that may be silently wrong. See [`WORKFLOW_SLOT_GAP_CHECK`](/docs/configuration/runtime-tuning#workflow_slot_gap_check).
|
|
43
|
+
- **An unreadable stored payload**: An event row still references a payload object, but the World reports that the object no longer exists in its storage. The same log would fail on every replay, so the run fails immediately instead of retrying forever.
|
|
35
44
|
|
|
36
45
|
## What to do
|
|
37
46
|
|
|
@@ -47,7 +56,7 @@ npm install workflow@latest
|
|
|
47
56
|
|
|
48
57
|
### 2. Retry the failed run
|
|
49
58
|
|
|
50
|
-
If this error
|
|
59
|
+
If this error reports replay divergence, automatic replay recovery has already been exhausted. If it reports an unreadable payload, recovery cannot recreate that payload. In either case, the run has been marked as `failed`. You can re-run the workflow using the **Re-run** button in the Workflow Dashboard; a re-run starts a new run with a new event log.
|
|
51
60
|
|
|
52
61
|
### 3. Report the issue
|
|
53
62
|
|
|
@@ -55,7 +64,7 @@ If the error persists after upgrading, [open an issue on GitHub](https://github.
|
|
|
55
64
|
|
|
56
65
|
- The version of the `workflow` package you are using
|
|
57
66
|
- The run ID(s) of the affected workflow run(s)
|
|
58
|
-
- The error message
|
|
67
|
+
- The complete error message, including any `eventType`, `correlationId`, `eventId`, or payload details
|
|
59
68
|
- Any details about the event log or the workflow that triggered the error
|
|
60
69
|
|
|
61
70
|
## This error cannot be caught
|
|
@@ -195,7 +195,7 @@ try {
|
|
|
195
195
|
| `MAX_DELIVERIES_EXCEEDED` | The run exceeded the maximum number of queue deliveries |
|
|
196
196
|
| `REPLAY_TIMEOUT` | A workflow replay exceeded the maximum allowed duration |
|
|
197
197
|
| `REPLAY_DIVERGENCE` | A replay could not consume the event log deterministically, usually because of non-deterministic workflow code. |
|
|
198
|
-
| `CORRUPTED_EVENT_LOG` | The event log contains orphaned or mismatched events
|
|
198
|
+
| `CORRUPTED_EVENT_LOG` | The event log cannot be replayed: it contains orphaned or mismatched events, or one of its stored payloads is no longer readable from the World's storage. If you see this, please [file an issue](https://github.com/vercel/workflow/issues) |
|
|
199
199
|
| `WORLD_CONTRACT_ERROR` | A World response violated the SDK contract; points at a World implementation bug |
|
|
200
200
|
| `RUNTIME_ERROR` | An internal runtime error. If you see this, please [file an issue](https://github.com/vercel/workflow/issues) |
|
|
201
201
|
|
package/docs/whats-new.mdx
CHANGED
|
@@ -33,7 +33,7 @@ The largest change in v5 has no API surface: the runtime does far less work per
|
|
|
33
33
|
|
|
34
34
|
**The runtime avoids waiting on the persistence layer where it can determine that is safe for your workload.** The runtime skips many API calls when they aren't needed, such as requesting the event log on a run's first invocation. Step creation is folded into step execution rather than being its own round trip. The inline loop consumes the event-log delta from the previous step's write instead of re-listing events. Each optimization is gated on specific runtime conditions and can be turned off individually. See [Runtime tuning](/docs/configuration/runtime-tuning).
|
|
35
35
|
|
|
36
|
-
**The workflow VM is kept alive across inline steps.** Within one invocation, a step-
|
|
36
|
+
**The workflow VM is kept alive across inline steps.** Within one invocation, a step-driven suspension keeps the live VM and hydrated state, including when hooks are open or created at the same boundary, so the next iteration appends only the newly written events instead of rebuilding the sandbox and replaying the whole log. Step inputs made of plain data or standard built-ins keep this fast path; see [`WORKFLOW_RETAINED_VM`](/docs/configuration/runtime-tuning#workflow_retained_vm).
|
|
37
37
|
|
|
38
38
|
**Resuming a hook takes one round trip instead of two.** `resumeHook()` writes the `hook_received` event and dispatches the queue message concurrently, with a `(runId, resumeId)` dedup constraint keeping the two writers converging on exactly one event. See [Resilient hook resumption](/docs/changelog/resilient-resume).
|
|
39
39
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "workflow",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.47",
|
|
4
4
|
"description": "Workflow SDK - Build durable, resilient, and observable workflows",
|
|
5
5
|
"main": "dist/typescript-plugin.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -59,18 +59,18 @@
|
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"ms": "2.1.3",
|
|
62
|
-
"@workflow/astro": "5.0.0-beta.
|
|
63
|
-
"@workflow/cli": "5.0.0-beta.
|
|
64
|
-
"@workflow/core": "5.0.0-beta.
|
|
65
|
-
"@workflow/errors": "5.0.0-beta.
|
|
62
|
+
"@workflow/astro": "5.0.0-beta.47",
|
|
63
|
+
"@workflow/cli": "5.0.0-beta.47",
|
|
64
|
+
"@workflow/core": "5.0.0-beta.47",
|
|
65
|
+
"@workflow/errors": "5.0.0-beta.19",
|
|
66
66
|
"@workflow/typescript-plugin": "5.0.0-beta.5",
|
|
67
|
-
"@workflow/utils": "5.0.0-beta.
|
|
68
|
-
"@workflow/next": "5.0.0-beta.
|
|
69
|
-
"@workflow/nest": "5.0.0-beta.
|
|
70
|
-
"@workflow/nitro": "5.0.0-beta.
|
|
71
|
-
"@workflow/nuxt": "5.0.0-beta.
|
|
72
|
-
"@workflow/sveltekit": "5.0.0-beta.
|
|
73
|
-
"@workflow/rollup": "5.0.0-beta.
|
|
67
|
+
"@workflow/utils": "5.0.0-beta.10",
|
|
68
|
+
"@workflow/next": "5.0.0-beta.47",
|
|
69
|
+
"@workflow/nest": "5.0.0-beta.47",
|
|
70
|
+
"@workflow/nitro": "5.0.0-beta.47",
|
|
71
|
+
"@workflow/nuxt": "5.0.0-beta.47",
|
|
72
|
+
"@workflow/sveltekit": "5.0.0-beta.47",
|
|
73
|
+
"@workflow/rollup": "5.0.0-beta.47"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@types/ms": "2.1.0",
|