You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 6f1e4c2
Browse filesBrowse the repository at this point in the historyBrowse files
Log the run id of each sequential-steps benchmark iteration alongside two Datadog APM links — the trigger request's trace and a span search for the run — so a suspicious STSO distribution can be opened in APM directly instead of hunted down by deployment id and time window.
Simplify hardened serialization: intrinsic captures assert at import instead of degrading per use, `URLSearchParams` now serializes natively on Node 18, and bound-function getters are reported as guest code (previously a getter defined via `fn.bind()` executed with an empty report).
Lazy hook resumption: on a fast path, `resumeHook()` writes the `hook_received` event and dispatches the workflow queue message concurrently instead of sequentially, cutting a round trip off resume latency. A `(runId, resumeId)` dedup constraint keeps the two writers converging on exactly one event; the runtime falls back to the sequential path when dedup support is unavailable or when `WORKFLOW_DISABLE_LAZY_HOOK_RESUME=1`. `resumeHook()` resolves to a `ResumedHook` (a `Hook` plus an optional `resilientResume: true` flag, set only when the direct write failed transiently and the resume was recovered via the queue consumer's re-ensure) — preserving the contract introduced alongside the resilient-resume work.
Surface an analytics event's `vercelId` in the run sidebar's Metadata list as a copyable "Request ID", and stop rendering the literal text `null` for a provenance id the analytics contract left empty. Also order Metadata rows missing from the panel's explicit order list after the listed ones instead of ahead of them, so a failed step's Error Code no longer renders above the step's own name.
Copy file name to clipboardExpand all lines: docs/content/docs/v5/api-reference/workflow-api/resume-hook.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -50,7 +50,7 @@ showSections={["parameters"]}
50
50
51
51
### Returns
52
52
53
-
Returns a `Promise<ResumedHook>`. `ResumedHook` extends`Hook` with an optional `resilientResume` flag, which is `true` when the `hook_received` event write failed with a transient error (429/5xx) and the payload will instead be delivered through the workflow queue (see the [resilient hook resume changelog](/docs/changelog/resilient-resume)). The `Hook`it extends resolves to:
53
+
Returns a `Promise<ResumedHook>` — a`Hook`extended with an optional `resilientResume` flag. Resolving means the resume was accepted and the workflow will continue, whether the `hook_received` event was written directly or, on the parallel fast path, delivered through the workflow queue for the runtime to materialize (see the [lazy hook resume changelog](/docs/changelog/resilient-resume)). `resilientResume` is `true` only when the direct event write failed transiently and the resume was recovered through the queue; on the happy path it is absent. The resolved hook:
Copy file name to clipboardExpand all lines: docs/content/docs/v5/changelog/resilient-resume.mdx
+7-7Lines changed: 7 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,16 +7,16 @@ description: resumeHook() now tolerates transient event storage failures, as lon
7
7
8
8
## Motivation
9
9
10
-
When event storage is degraded but the queue is up, `resumeHook()` previously failed entirely because the `hook_received`event write happens before the queue dispatch. This change brings `resumeHook()` to feature parity with [resilient `start()`](/docs/changelog/resilient-start): a transient event-write failure (429/5xx) no longer fails the resume when the payload can be delivered through the queue instead.
10
+
`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.
11
11
12
12
## Design
13
13
14
-
-`resumeHook()` writes the `hook_received` event first, then dispatches to the workflow queue. The order is deliberately sequential: dispatching in parallel could let the workflow runtime process the message and materialize a duplicate `hook_received` before the direct write commits.
15
-
-If the event write fails with a retryable error (429/5xx), the queue dispatch carries a `hookInput` payload — the dehydrated hook payload plus a client-minted `resumeId` idempotency key and the hook token. The workflow runtime materializes the missing `hook_received`event from `hookInput`during replay, deduplicating by `resumeId` in case the direct write actually committed or the queue message is redelivered.
16
-
- Replay itself also deduplicates: `hook_received` events sharing a `resumeId` belong to the same resume attempt, and only the first in the event log is delivered to workflow code. Even if concurrent queue redeliveries materialize the event twice, the payload reaches the workflow exactly once.
17
-
-If the eventwrite fails with any other error, or the queue dispatch fails, `resumeHook()` throws as before.
18
-
-`resumeHook()`now returns `ResumedHook` (exported from `workflow/api`), which extends `Hook` with an optional `resilientResume` flag. The flag is `true` when the fallback path was taken — the resume is accepted and will be delivered through the queue.
14
+
-On the fast path, `resumeHook()` writes the `hook_received` event and dispatches the workflow queue message **concurrently** (`Promise.allSettled`). The queue message carries a `hookInput` payload — the dehydrated hook payload plus a client-minted `resumeId` idempotency key, the hook token, and a payload digest.
15
+
-A `(runId, resumeId)` dedup constraint keeps the two writers converging on **exactly one**`hook_received` event: whichever lands first wins, and the other is resolved server-side as success rather than a duplicate. The queue consumer idempotently re-ensures the event from `hookInput`before replay, so the resume is guaranteed even if the direct write never commits.
16
+
- Replay also deduplicates: `hook_received` events sharing a `resumeId` belong to the same resume attempt, and only the first in the event log is delivered to workflow code. Even if a redelivery materializes the event twice, the payload reaches the workflow exactly once.
17
+
-**Queue dispatch failure is fatal** — the run was not re-triggered, so no consumer will re-ensure the event, and `resumeHook()` throws. A transient event-write failure (429/5xx, a transport error, or an expected `(runId, resumeId)` conflict with the consumer's own re-ensure) is swallowed because the queue delivery still guarantees the resume; a terminal run surfaces as `HookNotFoundError`, and any other event-write error is rethrown.
18
+
-`resumeHook()` returns `ResumedHook` (exported from `workflow/api`), which extends `Hook` with an optional `resilientResume` flag. The flag is `true`only when the direct write failed transiently and the resume was recovered through the queue; on the happy path and the sequential fallback it is absent.
19
19
20
20
## Compatibility
21
21
22
-
The resilient path is only taken when the target workflow run's deployment is recent enough to understand `hookInput` on the queue message. Because runs keep executing on the deployment they were created on, a resume that targets a run from an older deployment fails fast with the original error instead — the previous behavior, allowing the caller to retry.
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, legacy runs, or with `WORKFLOW_DISABLE_LAZY_HOOK_RESUME=1` — `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 simply uses the sequential path.
Copy file name to clipboardExpand all lines: docs/content/docs/v5/configuration/runtime-tuning.mdx
+7Lines changed: 7 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -38,6 +38,13 @@ For example, a workflow can run a 10-minute inline step even with `WORKFLOW_REPL
38
38
- Default: `3`
39
39
- Recovery replays before replay divergence is recorded as corruption.
40
40
41
+
### `WORKFLOW_DISABLE_LAZY_HOOK_RESUME`
42
+
43
+
- Default: enabled (lazy hook resume on)
44
+
- 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.
45
+
- The runtime falls back to the sequential path automatically 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 that resilience away to stay safe when dedup is not enforced, it does not preserve it.
46
+
- 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`.
0 commit comments