UNPKG

workflow

Version:

Workflow SDK - Build durable, resilient, and observable workflows

33 lines (24 loc) • 3.55 kB
--- title: Resilient hook resume description: resumeHook() now tolerates transient event storage failures when the queue is healthy instead of failing the resume. --- # Resilient `resumeHook()` <Callout type="info"> Superseded by [durable hook resume](/docs/changelog/lazy-hook-resume): `resumeHook()` now writes the `hook_received` event durably and only then publishes the workflow wake, so the two-writer design, the queue-carried payload, and the `resilientResume` flag described below are historical. The `(runId, resumeId)` constraint remains, converging transport-level retries of the producer's own write (and legacy `hookInput` redeliveries from older producers). </Callout> ## Motivation `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. ## Design - 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. - 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. - 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. - **Queue dispatch failure is fatal**: The run was not retriggered, 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. - `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. ## Compatibility 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.