Technology1 min read

Workflow SDK adds idempotent starts

By · Published by Everything Blog

In short

The updated `start()` function admits a workflow run and claims a hook in a single atomic operation, replacing the vulnerable lookup-then-start pattern. Duplicate attempts immediately fail by throwing a `HookConflictError` to safeguard system integrity. Furthermore, claiming the token enables early payload buffering via `resumeHook` before execution officially begins. Developers can access these improvements alongside an optional retention configuration by upgrading to version 5.3.1 of the workflow package.

Key points

  • Upgrading to version 5.3.1 of the workflow package delivers atomic run admission and hook…: Upgrading to version 5.3.1 of the workflow package delivers atomic run admission and hook claiming.
  • Early incoming payloads can be buffered in sequence prior to workflow execution through r…: Early incoming payloads can be buffered in sequence prior to workflow execution through resumeHook functionality.
  • Developers can configure experimental_minRetention to maintain token unavailability and b…: Developers can configure experimental_minRetention to maintain token unavailability and block late duplicate requests.

Sources checked by our analysis pipeline: 1

The Workflow SDK's start()

function can now admit a run and claim a hook in one atomic step. Duplicate workflow starts fail safely. The atomic start replaces the previous getHookByToken()

-then-start()

pattern, whose gap between the lookup and the start left a documented duplicate-start race.

import { resumeHook, start } from "workflow/api";import { HookConflictError } from "workflow/errors";import { processOrder } from "./workflows/process-order";

export async function POST(request: Request) { const { orderId, note } = await request.json(); const token = `order:${orderId}`;

try { const run = await start(processOrder, [orderId, note], { experimental_hook: { token }, }); return Response.json({ runId: run.runId, started: true }); } catch (error) { if (!HookConflictError.is(error)) throw error; // Route the duplicate's payload to the run that owns the token const hook = await resumeHook(token, { note }); return Response.json({ runId: hook.runId, started: false }); }}

Starting a run idempotently and routing a duplicate's payload to the run that already owns the token

Copy link to headingSubscribe to messages inside the run

The claimed Hook exists from the moment the run is admitted, so resumeHook

works even before…

Technology