workflow
5.2.3 · 20 Oct 2026Apache-2.01.1M / weekRepositorynpmAtom feed
56 stable releases, newest first, by release line. Notes are quoted as the maintainers published them.
- 5.x · latest line
5.2.3
20 Oct 2026- security
@pranaygp - Security: remove the
nanoiddependency (pinned at 5.1.6, affected by GHSA-28wg-ghj8-5hjv). Default hook tokens now come from a built-in copy of the same generator, so tokens and seeded random values stay identical for runs recorded on earlier versions. Upgradingnanoiditself would have changed them, becausenanoid@5.1.16draws a different number of random bytes per id. @pranaygp - Resolve a run's encryption key from metadata only, so
Run#getReadable()and resuming a hook stored without a resume context no longer make the server resolve the run's whole input and output first.@VaguelySerious - Fix cross-deployment
start()to 4.x deployments being misread as a run invocation, and keep replayed runs in the original run's region.
@workflow/core@5.2.3
- security
@pranaygp - Security: remove the
nanoiddependency (pinned at 5.1.6, affected by GHSA-28wg-ghj8-5hjv). Default hook tokens now come from a built-in copy of the same generator, so tokens and seeded random values stay identical for runs recorded on earlier versions. Upgradingnanoiditself would have changed them, becausenanoid@5.1.16draws a different number of random bytes per id. @pranaygp - Resolve a run's encryption key from metadata only, so
Run#getReadable()and resuming a hook stored without a resume context no longer make the server resolve the run's whole input and output first.@VaguelySerious - Fix cross-deployment
start()to 4.x deployments being misread as a run invocation, and keep replayed runs in the original run's region.@TooTallNate - Stamp the default snapshot threshold into QuickJS runs at
start()so they qualify for the event limit exemption, warn when a run approaches the event limit, and recommendWORKFLOW_VM=quickjsinMAX_EVENTS_EXCEEDEDandREPLAY_TIMEOUTerrors@VaguelySerious - Read event logs where a step's creation is recorded on its
step_startedalone, with no separatestep_created.@TooTallNate - Preserve
workflowVmandsnapshotThresholdfromexecutionContextinhydrateResourceIOfor observability UIs@karthikscale3 - Avoid redundant history reads when independent hooks register concurrently. Keep existing single-event requests parallel and reconcile their committed events together before continuing, with the normal history reload when the responses do not cover a complete consecutive suffix.
@VaguelySerious - Ignore events a step or hook writes after its outcome during replay instead of failing the run
@workflow/cli@5.0.6
@pranaygp - Resolve a run's encryption key from metadata only when decrypting in
workflow inspect --decryptand the web UI, instead of making the server resolve the run's whole input and output for each lookup.
@workflow/world@5.0.5
@VaguelySerious - Fix cross-deployment
start()to 4.x deployments being misread as a run invocation, and keep replayed runs in the original run's region.@TooTallNate - Exempt QuickJS runs with a positive snapshot threshold from the default per-run event limit, matching the Vercel World
@karthikscale3 - Avoid redundant history reads when independent hooks register concurrently. Keep existing single-event requests parallel and reconcile their committed events together before continuing, with the normal history reload when the responses do not cover a complete consecutive suffix.
@workflow/world-local@5.1.2
@NishitaSikka - Restore missing
run_createdevents for interrupted local run creation.@TooTallNate - Exempt QuickJS runs with a positive snapshot threshold from the default per-run event limit, matching the Vercel World
@AndrewBarba - Fix an edge case in
createHook({ experimental_force: true })where a racingresumeHookcould be dropped during the handover
@workflow/world-postgres@5.0.5
@VaguelySerious - Write
run_started,run_completed,run_failed,run_cancelled,attr_set,step_retrying,wait_createdandwait_completedin one transaction with the entity change they record. A process that died between the two used to leave an entity in a state its event log did not explain; a completed wait with nowait_completedmade the run retry the wait forever. A wait left in that state by an earlier version now gets its missing event on the next attempt.@dretanacr - Write a step's
step_completedorstep_failedrow update and its event in one transaction, asstep_startedalready does. A process that died between the two used to leave a terminal step with no terminal event, so the replayedstep_startedwas refused as terminal and the run never advanced.@pranaygp - Keep a checked-out client in the World's own
pgpool from ending the process when the database drops its connection.@pranaygp - Store timestamps as
timestamp with time zonesocreatedAtis correct when the Postgres server or session is not in UTC. Migration 0027 converts the columns and repairscreated_atvalues written by earlier versions.
@workflow/world-vercel@5.2.3
@VaguelySerious - Fix cross-deployment
start()to 4.x deployments being misread as a run invocation, and keep replayed runs in the original run's region.@karthikscale3 - Avoid redundant history reads when independent hooks register concurrently. Keep existing single-event requests parallel and reconcile their committed events together before continuing, with the normal history reload when the responses do not cover a complete consecutive suffix.
@workflow/web@5.0.6
@pranaygp - Resolve a run's encryption key from metadata only when decrypting in
workflow inspect --decryptand the web UI, instead of making the server resolve the run's whole input and output for each lookup.@TooTallNate - Recommend the QuickJS VM with snapshotting on runs that failed with
MAX_EVENTS_EXCEEDEDorREPLAY_TIMEOUT
@workflow/web-shared@5.0.6
@VaguelySerious - Read event logs where a step's creation is recorded on its
step_startedalone, with no separatestep_created.@TooTallNate - Add
getRunLimitGuidance()for runs that failed withMAX_EVENTS_EXCEEDEDorREPLAY_TIMEOUT, and show the run's workflow VM in the sidebar@VaguelySerious - Ignore events a step or hook writes after its outcome during replay instead of failing the run
@workflow/errors@5.0.5
@TooTallNate - Add a QuickJS snapshotting hint and docs link to
MaxEventsExceededError
5.2.2
13 Oct 2026@pranaygp - Order
setAttributes()resolution with the replay delivery barriers so a branch awaiting it can no longer resume out of event-log order, which could fail the run withREPLAY_DIVERGENCEor hand concurrent branches each other's step results@http-samc - Upgrade devalue to 6.0.3, which escapes large strings with native
JSON.stringifyinstead of a per-character loop. Strings containing unpaired surrogates now round-trip exactly instead of being replaced with U+FFFD.@VaguelySerious - Remove the experimental
WORKFLOW_RESILIENT_STEP_DISPATCHoption
@workflow/core@5.2.2
@pranaygp - Order
setAttributes()resolution with the replay delivery barriers so a branch awaiting it can no longer resume out of event-log order, which could fail the run withREPLAY_DIVERGENCEor hand concurrent branches each other's step results@http-samc - Upgrade devalue to 6.0.3, which escapes large strings with native
JSON.stringifyinstead of a per-character loop. Strings containing unpaired surrogates now round-trip exactly instead of being replaced with U+FFFD.@VaguelySerious - Remove the experimental
WORKFLOW_RESILIENT_STEP_DISPATCHoption
@workflow/world-vercel@5.2.2
@mmarufov - Fix
UND_ERR_REQ_CONTENT_LENGTH_MISMATCHby retrying HTTP stream appends with fresh bodies after confirmed 429s, using the shared throttle wait policy. Retry only connection-refused, DNS, and connect-timeout failures; surface other transport errors without replay.
5.2.1
12 Oct 2026@http-samc - Compress workflow arguments on the libuv threadpool when starting a run, so large inputs no longer block the caller's event loop during zstd compression.
@http-samc - Add a
compressArgumentsoption tostart(); passfalseto store the run's arguments uncompressed when the inputs are known to be incompressible.@pranaygp - Record the queue namespace on a run's
executionContext.queueNamespaceinstart(), so restart recovery re-enqueues the run to its own namespace.
@workflow/core@5.2.1
@http-samc - Compress workflow arguments on the libuv threadpool when starting a run, so large inputs no longer block the caller's event loop during zstd compression.
@http-samc - Add a
compressArgumentsoption tostart(); passfalseto store the run's arguments uncompressed when the inputs are known to be incompressible.@pranaygp - Record the queue namespace on a run's
executionContext.queueNamespaceinstart(), so restart recovery re-enqueues the run to its own namespace.
@workflow/cli@5.0.4
@pranaygp - Improve
workflow inspectfor long runs and scoped lookups: add--allfor complete steps, events, and sleeps listings, print reusable cursor hints, and keep pagination on the read path that issued the cursor. Support event ID and short workflow-name lookups, require--runIdfor individual steps, events, and streams, fixstto mean streams, include run IDs in stream hints, and exit non-zero when a stream read fails.
@workflow/world@5.0.4
@VaguelySerious - Add
InBandSupersededErrorand optionalinBand/expectedSeqInBandevent-create params for the in-band writer fence.@workflow/world-vercelforwards them, does not retry a fenced write in-process, and records the fence on its event spans. The runtime does not use them yet.@pranaygp - Add optional
Queue.registerRoute()andQueue.registerHandler(), implemented by@workflow/world-localand@workflow/world-postgres, so a queue consumer other than the Workflow SDK receives its own prefix's messages on its own route or in process.reenqueueActiveRuns()re-enqueues a run to the queue namespace it recorded, and@workflow/world-postgreskeeps a job's queue prefix.@VaguelySerious - Implement the optional in-band writer fence and list snapshot in
@workflow/world-localand@workflow/world-postgres, declared with the newinBandFencecapability.@VaguelySerious - Report
deliveryCountandcreatedAtin queue handler meta on@workflow/world-localand@workflow/world-postgres.@pranaygp -
@workflow/world-verceldeclares theinBandFencecapability (its backend refuses a stale in-band write on every run with an event sequencer, and answers an in-band write on an older run with a 400), and passes on the in-band counts its backend reports:snapshotonevents.listandallocatedon a singleevents.create. TheinBandFencecontract lets a World list no snapshot when its refusals carry both counters, and@workflow/world-testingtakes the counters from a refusal for such a World.
@workflow/world-local@5.1.1
@pranaygp - Add optional
Queue.registerRoute()andQueue.registerHandler(), implemented by@workflow/world-localand@workflow/world-postgres, so a queue consumer other than the Workflow SDK receives its own prefix's messages on its own route or in process.reenqueueActiveRuns()re-enqueues a run to the queue namespace it recorded, and@workflow/world-postgreskeeps a job's queue prefix.@VaguelySerious - Implement the optional in-band writer fence and list snapshot in
@workflow/world-localand@workflow/world-postgres, declared with the newinBandFencecapability.@fazxes - Fix
initDataDir()crashing on Bun for a new data directory: Bun'sopendir()reports a missing directory on the first read, not when it is opened.@pranaygp - Fix in-band event count conflicts when multiple processes write events, and fix count not surviving world restarts
@pranaygp - Register a run's stream names under a lock so
streams.listno longer drops a stream when several of the run's streams first write at once. A stream written again afterclear()is listed again.@VaguelySerious - Report a message's cumulative
deliveryCountacross{ timeoutSeconds }redeliveries on@workflow/world-postgres.@VaguelySerious - Report
deliveryCountandcreatedAtin queue handler meta on@workflow/world-localand@workflow/world-postgres.
@workflow/world-postgres@5.0.4
@VaguelySerious - Fix a process crash when the database closes an idle connection
@pranaygp - Add optional
Queue.registerRoute()andQueue.registerHandler(), implemented by@workflow/world-localand@workflow/world-postgres, so a queue consumer other than the Workflow SDK receives its own prefix's messages on its own route or in process.reenqueueActiveRuns()re-enqueues a run to the queue namespace it recorded, and@workflow/world-postgreskeeps a job's queue prefix.@VaguelySerious - Implement the optional in-band writer fence and list snapshot in
@workflow/world-localand@workflow/world-postgres, declared with the newinBandFencecapability.@VaguelySerious - Report a message's cumulative
deliveryCountacross{ timeoutSeconds }redeliveries on@workflow/world-postgres.@pranaygp - Honor
pagination.sortOrderinruns.listandsteps.list, which always returned newest first.@VaguelySerious - Report
deliveryCountandcreatedAtin queue handler meta on@workflow/world-localand@workflow/world-postgres.
@workflow/world-vercel@5.2.1
@VaguelySerious - Add
InBandSupersededErrorand optionalinBand/expectedSeqInBandevent-create params for the in-band writer fence.@workflow/world-vercelforwards them, does not retry a fenced write in-process, and records the fence on its event spans. The runtime does not use them yet.@pranaygp -
@workflow/world-verceldeclares theinBandFencecapability (its backend refuses a stale in-band write on every run with an event sequencer, and answers an in-band write on an older run with a 400), and passes on the in-band counts its backend reports:snapshotonevents.listandallocatedon a singleevents.create. TheinBandFencecontract lets a World list no snapshot when its refusals carry both counters, and@workflow/world-testingtakes the counters from a refusal for such a World.
@workflow/world-testing@5.0.4
@VaguelySerious - Implement the optional in-band writer fence and list snapshot in
@workflow/world-localand@workflow/world-postgres, declared with the newinBandFencecapability.@pranaygp -
@workflow/world-verceldeclares theinBandFencecapability (its backend refuses a stale in-band write on every run with an event sequencer, and answers an in-band write on an older run with a 400), and passes on the in-band counts its backend reports:snapshotonevents.listandallocatedon a singleevents.create. TheinBandFencecontract lets a World list no snapshot when its refusals carry both counters, and@workflow/world-testingtakes the counters from a refusal for such a World. - workflow@5.2.1
@workflow/errors@5.0.4
@VaguelySerious - Add
InBandSupersededErrorand optionalinBand/expectedSeqInBandevent-create params for the in-band writer fence.@workflow/world-vercelforwards them, does not retry a fenced write in-process, and records the fence on its event spans. The runtime does not use them yet.
@workflow/utils@5.0.2
@pranaygp - Add optional
Queue.registerRoute()andQueue.registerHandler(), implemented by@workflow/world-localand@workflow/world-postgres, so a queue consumer other than the Workflow SDK receives its own prefix's messages on its own route or in process.reenqueueActiveRuns()re-enqueues a run to the queue namespace it recorded, and@workflow/world-postgreskeeps a job's queue prefix.
5.2.0
8 Oct 2026- security
@TooTallNate - Upgrade
react-routerand@react-router/*to 7.18.4 to address CVE-2026-34077 and GHSA-qwww-vcr4-c8h2 @pranaygp - Start the queued branches of a wide
Promise.allfan-out sooner. The batched fan-out now commits its writes increateBatchcalls of at most 16 events instead of 32. Each queued branch's message waits on its chunk's commit and then its chunk's batched publish, and both are faster for smaller chunks. In production sweeps, the last branch of a 64-branch fan-out started about 90 ms sooner at p50, and of a 128-branch fan-out about 200 ms sooner, with no change to the first branch at those widths.@VaguelySerious - Deliver a hook payload to the pending await when an earlier
Promise.raceover the same hook was lost
@workflow/core@5.2.0
@pranaygp - Start the queued branches of a wide
Promise.allfan-out sooner. The batched fan-out now commits its writes increateBatchcalls of at most 16 events instead of 32. Each queued branch's message waits on its chunk's commit and then its chunk's batched publish, and both are faster for smaller chunks. In production sweeps, the last branch of a 64-branch fan-out started about 90 ms sooner at p50, and of a 128-branch fan-out about 200 ms sooner, with no change to the first branch at those widths.@VaguelySerious - Deliver a hook payload to the pending await when an earlier
Promise.raceover the same hook was lost@TooTallNate - Reject lifecycle registration inside steps, preserve reporting diagnostics for unreadable errors, and document executor registration and stream-lifetime constraints.
@TooTallNate - Enable VM-memory snapshotting by default for the QuickJS engine, with a threshold of 1000 events. Set
WORKFLOW_SNAPSHOT_THRESHOLD=0to opt out. Runs without an encryption key (for example on world-local and world-postgres) are still not snapshotted unlessWORKFLOW_SNAPSHOT_ALLOW_UNENCRYPTED=1is set.@VaguelySerious - Record on the invocation trace whether out-of-band events could affect each inline step boundary
@pranaygp - Hold a turbo step's awaited
step_starteduntil the backgroundedrun_startedlands, so an explicitWORKFLOW_OPTIMISTIC_INLINE_START=0no longer gets its first step rejected for a run that has not started yet.
@workflow/cli@5.0.3
@withkarann - Ship an oclif command manifest so the CLI loads only the command being run, and skip loading the runtime on exit when no World was created.
workflow --versionstarts in about 50 ms instead of 650 ms.
@workflow/world@5.0.3
@karthikscale3 - Reduce redundant schema compilation by compiling final event schemas only and reusing compiled v4 event-response and paginated response schemas.
@workflow/world-local@5.1.0
@ruiconti - Store event and step files in one directory per run for faster reads. Local run data written by earlier releases is deleted on upgrade.
@pranaygp - Make a tagged
clear()delete only its own tag's lock files, so it no longer reopens another tag's disposed hooks mid-run.
@workflow/world-vercel@5.2.0
@alangenfeld - Stream writes now upgrade to WebSocket by default; set
WORKFLOW_STREAMS_TRANSPORT=httpto opt out.@karthikscale3 - Reduce redundant schema compilation by compiling final event schemas only and reusing compiled v4 event-response and paginated response schemas.
@workflow/web@5.0.3
- security
@TooTallNate - Upgrade
react-routerand@react-router/*to 7.18.4 to address CVE-2026-34077 and GHSA-qwww-vcr4-c8h2
@workflow/swc-plugin@5.0.1
@karthikscale3 - Stabilize nested step IDs across scopes and SWC transform modes. Self-hosted runs that already recorded one of the previously colliding IDs may report a replay divergence if they continue on upgraded code; restart those in-flight runs after upgrading.
5.1.0
6 Oct 2026@wycats - Export the existing
Serializabletype from@workflow/coreandworkflow.@TooTallNate - Remove the unused, stale
vm-serde-bundle.generated.tsleft over from the retired in-VM QuickJS serializer.@alangenfeld - Wait for run creation before writing to writable streams passed as arguments to turbo steps.
@workflow/core@5.1.0
@wycats - Export the existing
Serializabletype from@workflow/coreandworkflow.@TooTallNate - Remove the unused, stale
vm-serde-bundle.generated.tsleft over from the retired in-VM QuickJS serializer.@alangenfeld - Wait for run creation before writing to writable streams passed as arguments to turbo steps.
@alangenfeld - Mark
step_failedwrites that record a failed step body withafterStepBody, so a World can keep waiting out a throttled write instead of re-running the body.@alangenfeld - Release drained stream writer sockets without closing the shared stream, preserve handle reuse over HTTP, and dispose transports when public writable streams abort. Propagate source failures to readers of flushable stream pipes.
@TooTallNate - Serialize only the viewed bytes of a
Float16Array(no longer the whole NodeBufferpool behind it), and supportFloat16Arrayin the QuickJS engine with the same wire format as the node:vm engine.@VaguelySerious - Fix cross-deployment hook resumes and starts writing zstd payloads to runs on Node.js versions that cannot decode them. Required for upgrading to a newer Node.js version while existing runs on older Node.js versions are still receiving hooks.
@workflow/world@5.0.2
@alangenfeld - Mark
step_failedwrites that record a failed step body withafterStepBody, so a World can keep waiting out a throttled write instead of re-running the body.@alangenfeld - Release drained stream writer sockets without closing the shared stream, preserve handle reuse over HTTP, and dispose transports when public writable streams abort. Propagate source failures to readers of flushable stream pipes.
@VaguelySerious - Fix cross-deployment hook resumes and starts writing zstd payloads to runs on Node.js versions that cannot decode them. Required for upgrading to a newer Node.js version while existing runs on older Node.js versions are still receiving hooks.
@workflow/world-local@5.0.2
@alangenfeld - Prevent duplicate hook creation events when a retry publishes before the original token-claim owner.
@workflow/world-postgres@5.0.2
@VaguelySerious - Reconnect the stream and run-status
LISTENconnection after the database drops it, instead of crashing the process.
@workflow/world-vercel@5.1.0
@VaguelySerious - Default the events transport to WebSockets. Set
WORKFLOW_EVENTS_TRANSPORT=httpto opt out.@pranaygp - Upgrade
@vercel/queueto 0.6.0 so queue callbacks for messages that are already claimed or processed (409 / 410) respond 200 instead of loggingQueue callback errorand returning 500@alangenfeld - Wait out a throttled (429) write recording a step body's outcome until the invocation's deadline instead of 30 seconds, and resend a throttled event-log read from its cursor instead of restarting it.
@alangenfeld - Release drained stream writer sockets without closing the shared stream, preserve handle reuse over HTTP, and dispose transports when public writable streams abort. Propagate source failures to readers of flushable stream pipes.
@VaguelySerious - Upgrade
@vercel/queueto 0.8.0 so throttled queue sends honor the server'sRetry-Afterand queue errors carry clean messages
@workflow/web@5.0.2
@mitul-s - De-duplicate separate shadcn button implementations between
@workflow/web-sharedand@workflow/web
@workflow/web-shared@5.0.2
@mitul-s - De-duplicate separate shadcn button implementations between
@workflow/web-sharedand@workflow/web
@workflow/builders@5.0.2
@VaguelySerious - Fail the build when the workflow bundle still contains
require(). The workflow sandbox has norequire, so an import esbuild left external (most often a Node.js builtin reached through a transitive dependency or a re-export) or an unresolved dynamicrequire()used to produce a bundle that threwReferenceError: require is not definedon its first load. The build now reports the specifier, the module that imported it and the import chain back to user code. Arequire()inside atry/catchblock, or behind atypeof requirecheck, is left alone, since that is how packages probe for an optional dependency and the call either fails harmlessly or never runs. SetWORKFLOW_ALLOW_UNSAFE_FLOW_BUNDLE=1to downgrade the failure to a warning.
@workflow/next@5.0.2
@VaguelySerious - Fail the build when the workflow bundle still contains
require(). The workflow sandbox has norequire, so an import esbuild left external (most often a Node.js builtin reached through a transitive dependency or a re-export) or an unresolved dynamicrequire()used to produce a bundle that threwReferenceError: require is not definedon its first load. The build now reports the specifier, the module that imported it and the import chain back to user code. Arequire()inside atry/catchblock, or behind atypeof requirecheck, is left alone, since that is how packages probe for an optional dependency and the call either fails harmlessly or never runs. SetWORKFLOW_ALLOW_UNSAFE_FLOW_BUNDLE=1to downgrade the failure to a warning.
@workflow/utils@5.0.1
@pranaygp -
sleep(),RetryableError'sretryAfter, and a hook'sexperimental_minRetentionnow throw on an Invalid Date, or on a duration that ends past the latest time aDatecan hold, instead of sending the Invalid Date to the World.
@workflow/nest@5.1.0
@pranaygp - Harden the NestJS integration against the framework features a real application uses. Fastify applications now boot correctly, queue deliveries are no longer capped at 100,
app.enableVersioning()no longer moves the workflow routes,setGlobalPrefix(prefix, { exclude })is honoured,isWorkflowRequest(context)export added, the Vercel app function is bundled for the runtime it is deployed on,--external <pkgs>is honored, and the builder warns when a second copy of@nestjs/coreis present
5.0.1
1 Oct 2026@alangenfeld - Dynamic
start()refusals (opt-in, validation, same-deployment, World capability, execution-context budget) are now fatal, so astart()inside a step fails fast instead of retrying.@alangenfeld - Dynamic
start()now publishes the run only afterrun_createdconfirms its code was stored, so a refused or failed create can no longer leave an executing run behind.@alangenfeld - Dynamic
start()validation: capexportNameat 64 characters, detect"use step"only as a real directive, and name non-async, generator, and duplicate workflow declarations.
@workflow/core@5.0.1
@alangenfeld - Dynamic
start()refusals (opt-in, validation, same-deployment, World capability, execution-context budget) are now fatal, so astart()inside a step fails fast instead of retrying.@alangenfeld - Dynamic
start()now publishes the run only afterrun_createdconfirms its code was stored, so a refused or failed create can no longer leave an executing run behind.@alangenfeld - Dynamic
start()validation: capexportNameat 64 characters, detect"use step"only as a real directive, and name non-async, generator, and duplicate workflow declarations.@VaguelySerious - Stop re-executing a running lazy or pre-claimed inline step when its message is redelivered.
@VaguelySerious - Fix
nuxt devand other Nitro v2 dev servers failing with a Rollup "already an existing non-external module id" error, and with "Workflow world runtime was not initialized" after it.@TooTallNate - Add experimental threshold-based VM-memory snapshotting to the QuickJS engine via
WORKFLOW_SNAPSHOT_THRESHOLD(or per-runexecutionContext.snapshotThreshold). Once the configured number of events has been processed since the last snapshot, suspensions persist a compressed, encrypted VM snapshot, which is later used for resumption. Runs without an encryption key are only snapshotted withWORKFLOW_SNAPSHOT_ALLOW_UNENCRYPTED=1@VaguelySerious - Fix webhooks returning 404 on Nitro apps running Node 24, where serializing the request's headers failed.
@VaguelySerious - Errors with a read-only
stack, such as postgres.js query errors, are now reported as thrown instead of being replaced byCannot assign to read only property 'stack'.
@workflow/world@5.0.1
@TooTallNate - Add experimental threshold-based VM-memory snapshotting to the QuickJS engine via
WORKFLOW_SNAPSHOT_THRESHOLD(or per-runexecutionContext.snapshotThreshold). Once the configured number of events has been processed since the last snapshot, suspensions persist a compressed, encrypted VM snapshot, which is later used for resumption. Runs without an encryption key are only snapshotted withWORKFLOW_SNAPSHOT_ALLOW_UNENCRYPTED=1@TooTallNate - Add an optional, experimental
experimental_snapshotsstorage interface for the QuickJS engine's VM-memory snapshotting (save/load/deleteplusSnapshotMetadata, andencodeSnapshotEnvelope/decodeSnapshotEnvelope.
@workflow/world-local@5.0.1
@TooTallNate - Add an optional, experimental
experimental_snapshotsstorage interface for the QuickJS engine's VM-memory snapshotting (save/load/deleteplusSnapshotMetadata, andencodeSnapshotEnvelope/decodeSnapshotEnvelope.
@workflow/world-postgres@5.0.1
@cwdx - Add
pollIntervaloption andWORKFLOW_POSTGRES_POLL_INTERVAL_MSenv var for the Graphile Worker poll interval (default500).@TooTallNate - Add an optional, experimental
experimental_snapshotsstorage interface for the QuickJS engine's VM-memory snapshotting (save/load/deleteplusSnapshotMetadata, andencodeSnapshotEnvelope/decodeSnapshotEnvelope.
@workflow/world-vercel@5.0.1
@SandboxRD - Fix
createQueueDispatcher()andcreateEventsDispatcher()crashing under Bun withcompose is not a function. Bun resolvesundicito its built-in module, whose dispatchers can't compose interceptors and are ignored by Bun'sfetch, so the plain dispatcher is used instead.@alangenfeld - The dynamic execution-context budget error now says that step IDs and
exportNamecount, not only aliases.@TooTallNate - Add an optional, experimental
experimental_snapshotsstorage interface for the QuickJS engine's VM-memory snapshotting (save/load/deleteplusSnapshotMetadata, andencodeSnapshotEnvelope/decodeSnapshotEnvelope.@shalabhc - Add
WORKFLOW_EVENTS_TRANSPORT_WS_OVERRIDE_WORKFLOWS, a comma-separated list of workflows whose runs use the WebSocket events transport even whenWORKFLOW_EVENTS_TRANSPORTishttpor unset.@shalabhc - The WebSocket events transport now limits the size of each message. Any frame over
WORKFLOW_WS_MAX_MESSAGE_BYTES(default 12 MiB, at most 16 MiB) is sent as several messages and rebuilt by the receiver. The client offersframe-partsin thex-workflow-ws-flagsupgrade header so the backend can split large replies too.@alangenfeld - Split stream WebSocket write groups by encoded message bytes as well as chunk count, bounded by
WORKFLOW_WS_MAX_MESSAGE_BYTES(default 12 MiB, at most 16 MiB).@alangenfeld - Retry throttled (429) stream WebSocket writes and closes after
Retry-After, moving to HTTP when the socket closes or the wait budget runs out, and retry a close 5xx over HTTP instead of failing the writer.
@workflow/ai@5.0.1
@VaguelySerious - Publish the 5.x release of
@workflow/ai. The5.0.0on npm is an old pre-release build that requiresworkflow4.x; use5.0.1or later withworkflow5.
@workflow/builders@5.0.1
@withkarann - Fix directive discovery missing
"use step"and"use workflow"when a backtick appears earlier in a comment or regex literal@VaguelySerious - Keep JSON import attributes in generated bundles, and stop bundling the builder's own serde checker into step and workflow bundles
@VaguelySerious - Fix
next devnot bundling a workflow that is created after its import, or deleted and restored
@workflow/next@5.0.1
@VaguelySerious - Fix
next devnot bundling a workflow that is created after its import, or deleted and restored@pranaygp - Scope the Next dev watcher to the framework module graph and stop opening a file descriptor per source file, which fixes file watch limits being reached on large projects
@workflow/nitro@5.0.1
@VaguelySerious - Fix
nuxt devand other Nitro v2 dev servers failing with a Rollup "already an existing non-external module id" error, and with "Workflow world runtime was not initialized" after it.
5.0.0
30 Sep 2026- breaking
**
hook.metadataon hooks returned bygetHookByToken()andresumeHook()is now a lazy getter that returns a Promise, likerun.returnValue, and needs to be awaited. Looking a hook up by token no longer pays for hydrating metadata that is never read. - breaking
Initial v5 beta release
- breaking
Remove the deprecated
experimental_setAttributesandExperimentalSetAttributesOptionsaliases; usesetAttributesandSetAttributesOptionsinstead.
Major Changes
- breaking
**
hook.metadataon hooks returned bygetHookByToken()andresumeHook()is now a lazy getter that returns a Promise, likerun.returnValue, and needs to be awaited. Looking a hook up by token no longer pays for hydrating metadata that is never read. - breaking
Initial v5 beta release
- breaking
Remove the deprecated
experimental_setAttributesandExperimentalSetAttributesOptionsaliases; usesetAttributesandSetAttributesOptionsinstead. - breaking
Remove
@workflow/core/privateandworkflow/internal/privatepublic subpath exports. The SWC compiler plugin no longer generates imports from these paths. - breaking
Remove the retired step route runtime and make queue health checks target the combined flow handler.
- breaking
Deterministic sandbox hardening:
crypto.subtle.digestin workflow functions now computes synchronously vianode:crypto(byte-identical values, deterministic timing under replay);Atomics.waitAsync, asyncWebAssemblycompilation,WeakRef, andFinalizationRegistryare no longer exposed (wall-clock timing and GC observation cannot be replayed).
Minor Changes
Add opt-in wire-level framing for byte streams (
type: 'bytes') so consumers can identify chunk boundaries — a prerequisite for transparent auto-reconnect. The framing decision is gated on a newframedByteStreamscapability and recorded per-stream in the serialized ref (framing: 'framed-v1'); legacy raw streams continue to work unchanged.start()now delegates run ID generation toworld.createRunId(options)when the world provides it, falling back to a monotonic ULID otherwise. The full options bag is passed through so worlds can read whichever fields they recognise. Adds a newregionoption that worlds may consume — when set, it is also forwarded onto the queue options so the initial workflow message is routed to the matching region.Add experimental dynamic workflows:
start()accepts workflow source as a string, compiles and stores it with the run through the run-payload serialization pipeline, and replays from that stored code. Steps are exposed to the source through an explicitexperimental_dynamic.stepsmap, which is not a security boundary: dynamic source runs with the deployment's full privileges. Off by default; a deployment opts in withWORKFLOW_EXPERIMENTAL_DYNAMIC_WORKFLOWS=1, and dynamic runs can only start on the current deployment.Strengthen the event-creation precondition guard: replay-context writes now also send the number of loaded events, so a snapshot that is missing an event is rejected instead of committing a divergent event log, and a rejection restarts the replay in-process (consuming the events a world may attach to the rejection) rather than re-committing the rejected payload or re-invoking over the queue.
@workflow/world-localand@workflow/world-postgresdo not implement the check.Add an opt-in optimistic-concurrency guard for event creation (
WORKFLOW_PRECONDITION_GUARD=1): replay-context event creations send astateUpdatedAtsnapshot timestamp, and the runtime reloads the event log and retries (then falls back to a queue re-invocation) when the backend reports a newer out-of-band event with a 412PreconditionFailedError.Surface
workflowCoreVersionfrom the responding deployment inhealthCheck()results.Replace
hook.hasConflict(aPromise<boolean>property) withhook.getConflict(), a method returning a promise that suspends the workflow to commit hook registration and resolves with the conflictingRunwhen another active hook owns the token (ornullonce the hook is registered), without waiting for hook payload data. Code usingawait hook.hasConflictshould migrate toconst conflict = await hook.getConflict()and branch onconflict !== null.Lazy hook resumption: on a fast path,
resumeHook()writes thehook_receivedevent 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 whenWORKFLOW_DISABLE_LAZY_HOOK_RESUME=1.resumeHook()resolves to aResumedHook(aHookplus an optionalresilientResume: trueflag, 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.Pin correlation-ID draw order to event-log order (Node.js VM engine), so two concurrent replays of the same run assign the same IDs even when one loaded a shorter event-log prefix. Set
WORKFLOW_LOG_ORDER_DRAWS=0to opt back into arrival-order delivery resolution.Allow passing initial run attributes through
start(), and speed up workflow-levelsetAttributecalls by using native events for recording attributes.Use custom class serialization for
Runacross runtime and workflow VM contexts, and add e2e coverage forRuninstance boundary roundtripsAdd
WORKFLOW_TRACE_MODEwith a newlinkeddefault: each workflow/step invocation span is now its own trace root with span links to the delivery and run-origin contexts, instead of one trace spanning the entire run. world-vercel now explicitly injects W3Ctraceparent/tracestate/baggageheaders on outgoing workflow-server requests.Span names are also friendlier: workflow and step spans now use the short function name (e.g.
workflow.execute processOrder,step.execute chargeCard,workflow.start processOrder) instead of the uppercase prefixes and full machine names (WORKFLOW_V2 workflow//./src/jobs/order//processOrder). The full name remains available in theworkflow.name/step.namespan attributes, and newworkflowDisplayName/stepDisplayNamehelpers are exported from@workflow/utils.Behavioral changes to telemetry under the new default (set
WORKFLOW_TRACE_MODE=continuousto restore the previous trace shape exactly; the span-name change applies in both modes):A run no longer shares one trace ID: the trace of the request that called
start()no longer contains the workflow's execution spans — navigate via span links or theworkflow.run.idattribute instead.Sampling decisions are made independently per invocation root (previously one parent-based decision covered the whole run), and the number of root spans/traces increases to one per invocation.
workflow.execute/step.executeinvocation spans (formerlyWORKFLOW_V2/STEP) become parentless roots, which changes parent/child-based queries and service-map edges.Re-enqueued queue messages forward the original run-origin trace carrier unchanged, rather than each invocation's current context.
Queries or dashboards matching the old
WORKFLOW_V2 .../STEP ...span names must switch to the new names.The queue-delivered
workflow.executespan kind changed frominternaltoconsumer, matching the queue-deliveredstep.executespan (this applies in both modes).
Existing attributes and baggage keys are unchanged, and everything remains a no-op when no OpenTelemetry SDK is registered.
Inline execution now runs up to
WORKFLOW_MAX_INLINE_STEPS(default 3) steps in parallel per suspension, each lazily created. An opt-inWORKFLOW_OPTIMISTIC_INLINE_START(default off) additionally starts step bodies beforestep_startedis confirmed, reconciling the in-flight start before the terminal write so a lost create-claim is discarded; it is off by default because under contention a step body can run more than once (e.g. two runs writing to the workflow stream can corrupt it), so only enable it for idempotent steps.The
WORKFLOW_PRECONDITION_GUARDevent-creation guard is now on by default; opt out withWORKFLOW_PRECONDITION_GUARD=0.Add an experimental QuickJS WASM VM engine for workflow execution, opt-in via
WORKFLOW_VM=quickjs(or per-runexecutionContext.workflowVm). The engine performs the same full event replay as the defaultnode:vmengine but runs workflow code in a QuickJS VM compiled to WebAssembly, enabling platforms withoutnode:vmsupport and laying the groundwork for VM-memory snapshotting.Rename
experimental_setAttributestosetAttributes— the attributes feature is no longer experimental. The old name remains available as a deprecated alias.Make
resumeHook()resilient to transienthook_receivedevent write failures (429/5xx) by carrying the payload on the queue message for the runtime to materialize. ReturnedHookgets a newresilientResume: trueflag when this fallback path is taken.Retain workflow execution across inline steps within one invocation;
WORKFLOW_RETAINED_VM=0disables retention. Boundaries whose step arguments are not primitive values fall back to ordinary replay.Retained workflow VMs now keep the fast path when step arguments are plain data or standard built-ins (
Map,Set,Date, typed arrays,URL,Headers, …), not just primitives. A boundary falls back to a normal replay only when serializing its arguments ran code the workflow controls — a getter, a proxy, a custom serializer — or computed anError's stack trace.Add
Run#getWritable()andrun.writableto append to an existing run's stream.Add an
allowReservedAttributesoption tostart()so framework-level callers can seed reserved$-prefixed run attributes at creation, matching the existingexperimental_setAttributesoption. The flag is carried through the resilient-start queue input so lazy run creation validates identically.Allow
start()to be called directly inside workflow functionsAdd a catchable
StreamErrorand classify Workflow stream infrastructure failures asSTREAM_ERRORinstead ofUSER_ERROR.A
WritableStreamfrom a workflow'sgetWritable()can now be passed as an argument to a child workflow viastart(); the child's writes land on the parent run's stream directly for the full lifetime of the child run.Add turbo mode (on by default, disable with
WORKFLOW_TURBO=0): on the first delivery of a run's first invocation the runtime backgroundsrun_started, skips the initial event-log load, and forces optimistic inline start so the run reaches its first steps with no preceding network round-trips. It is safe there because the first delivery has no concurrent handler to race; turbo mode deactivates once a hook or sleep is encountered.Add
registerLifecycleHooksfromworkflow/apifor best-effort completion and failure reporting with a read-free workflow name, lazyRuninstance, and failure cause hydrated from the persisted payload. Run metadata getters avoid resolving input/output payloads.
Patch Changes
Add
experimental_setAttributes()workflow-level helper for attaching string key/value metadata to a workflow run, surfaced asrun.attributesLog ignored duplicate events at
debuginstead ofinfo/error, so a straggler no longer prints on every replay of the run.Speed up Next.js workflow dev rebuilds, ignore commented imports during HMR discovery, and avoid Turbopack resolving custom-world dynamic imports.
Embed source content in published sourcemaps.
Fix spurious "Event cursor missing after initial load" warning
Fix
workflow/nextESM compatibility fornext.config.mjsfilesRoute Workflow AI examples through AI Gateway and recommend WorkflowAgent for Workflow 5.
Fix
getWritable()returning a new TransformStream per call, which caused racing pipes to reorder chunks when callers acquired a writer per write. Repeat calls within the same step now share a single pipe per(runId, namespace).Simplify hardened serialization: intrinsic captures assert at import instead of degrading per use,
URLSearchParamsnow serializes natively on Node 18, and bound-function getters are reported as guest code (previously a getter defined viafn.bind()executed with an empty report).Serializing values built inside the
node:vmworkflow VM no longer executes workflow code, using engine brand checks and host intrinsics captured at boot.Skip the per-step incremental
events.listround-trip in the inline sequential loop by consuming an event-log delta returned from the step's terminal write (gated to the single-step case with no open hooks or waits).Add the opt-in
CreateEventParams.sinceCursorcontract so a step-terminalevents.createcan return the event-log delta since that cursor (viaEventResult.events/cursor/hasMore).Return the inline delta from a step-terminal write when
sinceCursoris supplied, computed identically toevents.listso the consumed prefix cannot skew from the server log.Forward
sinceCursorover the v4 wire in@workflow/world-vercelso the server can return the delta on a step-terminal response; older servers ignore it and the runtime falls back toevents.list.Lazy inline step start: the owned-inline runtime path now sends a single
step_startedcarrying the step input, letting the world create the step on the fly and saving one round-trip per inline step.@workflow/world:step_startedevent data accepts an optionalinput, andEventResultgains astepCreatedownership signal.@workflow/world-local:step_startedwith input atomically creates the step plus a syntheticstep_createdevent; a lazystep_startedfor an already-existing step throwsEntityConflictErrorso concurrent losers skip (exactly-once).@workflow/world-postgres: same lazy-create + exactly-once create-claim for the Postgres backend.@workflow/world-vercel: sends the step input onstep_startedover the v4 wire and threads the server'sstepCreatedsignal intoEventResult.Make
run.inputandstep.input.optional()on the World snapshot schemas so consumers no longer fail validation when the service externalizes payloads asRemoteRefblobs.Fix a replay-determinism gap where branch wake order — and therefore step correlation ids — could depend on how much of the event log an invocation had loaded, corrupting runs under concurrent replays (CORRUPTED_EVENT_LOG).
Memoize hydrated step return values across inline replay iterations, turning the per-invocation step-result decrypt+parse cost from O(N²) to O(N) for sequential workflows. Only primitive results are cached, so deterministic replay is preserved.
Prepare encrypted replay payloads concurrently and cache the decrypted, decompressed representation for reuse across inline replay iterations.
QuickJS engine performance: cache compiled WebAssembly modules process-wide, and execute steps inline in a live-VM continuation loop (no queue round-trip per step, cheap events fed before step bodies, delayed wait-continuation dispatch for racing timers).
Rename
useworkflow.devURLs toworkflow-sdk.devRevert the static workflow world target injection: the world package is again resolved at runtime from
WORKFLOW_TARGET_WORLDinstead of being aliased into host bundles at build time.Add serializable
AbortControllerandAbortSignalsupport across workflow and step boundaries. Workflow code can now construct anAbortController, passsignalto steps, and callabort().Behavior change:
AbortErrorthrown from inside a step is now wrapped asFatalErrorand skips retry semantics. As a result, custom timeouts onfetchinside steps are no longer re-tried by default, and now need to be wrapped inRetryableErrorto preserve the old behavior.Fix stream IDs minted while serializing a step's arguments latching the host wall clock into a run's ID sequence, which gave every entity created afterwards a different correlation ID on each replay
Allow
experimental_setAttributes()to be called from step functions.- security
Bump
devalueto 5.8.1 to address published security advisories. Merge flow and step routes into a single combined handler that executes steps inline when possible, reducing function invocations and queue overhead.
Use inline sourcemaps for all workspace packages; published packages no longer ship external
.js.mapfiles.Standardize first-party World packages on
createWorld(), support relative target World modules consistently, and align the Postgres WorldDATABASE_URLfallback with bootstrap.Fix
world.tsbeing tree-shaken out of the bundle and unavailable at runtimeUpgrade runtime validation to Zod 4.5 and enable compilation on SDK-owned Zod schemas.
- 4.x
4.8.14
21 Oct 2026- security
@pranaygp - Security: remove the
nanoiddependency (pinned at 5.1.6, affected by GHSA-28wg-ghj8-5hjv). Default hook tokens now come from a built-in copy of the same generator, so tokens and seeded random values stay identical for runs recorded on earlier versions. Upgradingnanoiditself would have changed them, becausenanoid@5.1.16draws a different number of random bytes per id. @pranaygp -
workflow inspect streamexits 1 when it cannot read or decode a stream, andworkflow inspect stlists streams instead of steps.@pranaygp - Register a run's stream names under a lock so
listStreamsByRunIdno longer drops a stream when several of the run's streams first write at once. A stream written again afterclear()is listed again.
@workflow/core@4.8.14
- security
@pranaygp - Security: remove the
nanoiddependency (pinned at 5.1.6, affected by GHSA-28wg-ghj8-5hjv). Default hook tokens now come from a built-in copy of the same generator, so tokens and seeded random values stay identical for runs recorded on earlier versions. Upgradingnanoiditself would have changed them, becausenanoid@5.1.16draws a different number of random bytes per id.
@workflow/cli@4.3.18
@pranaygp -
workflow inspect streamexits 1 when it cannot read or decode a stream, andworkflow inspect stlists streams instead of steps.
@workflow/world-local@4.4.4
@pranaygp - Register a run's stream names under a lock so
listStreamsByRunIdno longer drops a stream when several of the run's streams first write at once. A stream written again afterclear()is listed again.
@workflow/world-postgres@4.3.11
@VaguelySerious - Write
run_started,run_completed,run_failed,run_cancelled,step_retrying,wait_createdandwait_completedin one transaction with the entity change they record. A process that died between the two used to leave an entity in a state its event log did not explain; a completed wait with nowait_completedmade the run retry the wait forever. A wait left in that state by an earlier version now gets its missing event on the next attempt.@dretanacr - Write a step's
step_completedorstep_failedrow update and its event in one transaction, asstep_startedalready does. A process that died between the two used to leave a terminal step with no terminal event, so the replayedstep_startedwas refused as terminal and the run never advanced.@VaguelySerious - Fix a process crash when the database closes an idle connection
@pranaygp - Honor
pagination.sortOrderinruns.listandsteps.list, which always returned newest first.@pranaygp - Keep a checked-out client in the World's own
pgpool from ending the process when the database drops its connection.@pranaygp - Store timestamps as
timestamp with time zonesocreatedAtis correct when the Postgres server or session is not in UTC. Migration 0027 converts the columns and repairscreated_atvalues written by earlier versions.
@workflow/world-vercel@4.7.8
@mmarufov - Fix
UND_ERR_REQ_CONTENT_LENGTH_MISMATCHby retrying HTTP stream appends with fresh bodies after confirmed 429s, with a boundedRetry-Afterwait. Retry only connection-refused, DNS, and connect-timeout failures; surface other transport errors without replay.
4.8.13
8 Oct 2026- security
@TooTallNate - Upgrade
react-routerand@react-router/*to 7.18.4 to address CVE-2026-34077 and GHSA-qwww-vcr4-c8h2 @shalabhc - Accept lazy completed and failed runs whose payload is represented by a remote reference.
@shalabhc - Accept lazy completed and failed runs whose payload is represented by a remote reference.
@workflow/core@4.8.13
@shalabhc - Accept lazy completed and failed runs whose payload is represented by a remote reference.
@workflow/world@4.5.1
@shalabhc - Accept lazy completed and failed runs whose payload is represented by a remote reference.
@workflow/world-local@4.4.3
@pranaygp - Make a tagged
clear()delete only its own tag's lock files, so it no longer reopens another tag's disposed hooks mid-run.
@workflow/web@4.1.29
- security
@TooTallNate - Upgrade
react-routerand@react-router/*to 7.18.4 to address CVE-2026-34077 and GHSA-qwww-vcr4-c8h2
4.8.12
2 Oct 2026@VaguelySerious - Fix an issue with the workflow's deterministic clock tracking advancement on consumption, not on write, which could lead to a determinism issue when concurrent replays called
Date.nowwith different amounts of events read from the log@gaojude - Order hook registration and conflict settlements with earlier workflow deliveries so replay preserves concurrent step correlation IDs.
@AndrewBarba - Allow same-run queue wakes to reach a workflow while an inline step is pending, so hook-driven cancellation and steering do not wait for the step to finish.
@workflow/core@4.8.12
@VaguelySerious - Fix an issue with the workflow's deterministic clock tracking advancement on consumption, not on write, which could lead to a determinism issue when concurrent replays called
Date.nowwith different amounts of events read from the log@gaojude - Order hook registration and conflict settlements with earlier workflow deliveries so replay preserves concurrent step correlation IDs.
@workflow/world-postgres@4.3.9
@AndrewBarba - Allow same-run queue wakes to reach a workflow while an inline step is pending, so hook-driven cancellation and steering do not wait for the step to finish.
@pranaygp - Document that this is a reference implementation whose workflow HTTP routes are unauthenticated, and how to restrict them.
4.8.11
1 Oct 2026@SandboxRD - Fix
createQueueDispatcher()andcreateEventsDispatcher()crashing under Bun withcompose is not a function. Bun resolvesundicito its built-in module, whose dispatchers can't compose interceptors and are ignored by Bun'sfetch, so the plain dispatcher is used instead.@alangenfeld - Honor server-provided Retry-After delays when scheduling queue redeliveries.
@withkarann - Fix directive discovery missing
"use step"and"use workflow"when a backtick appears earlier in a comment or regex literal
@workflow/world-vercel@4.7.6
@SandboxRD - Fix
createQueueDispatcher()andcreateEventsDispatcher()crashing under Bun withcompose is not a function. Bun resolvesundicito its built-in module, whose dispatchers can't compose interceptors and are ignored by Bun'sfetch, so the plain dispatcher is used instead.@alangenfeld - Honor server-provided Retry-After delays when scheduling queue redeliveries.
@workflow/builders@4.1.16
@withkarann - Fix directive discovery missing
"use step"and"use workflow"when a backtick appears earlier in a comment or regex literal@VaguelySerious - Keep JSON import attributes in generated bundles, and stop bundling the builder's own serde checker into step and workflow bundles
@workflow/vite@4.0.26
@alangenfeld - Rebuild workflows once per file change instead of once per Vite environment.
4.8.10
30 Sep 2026@pranaygp - Fix an unhandled rejection that could exit the process when the encryption-key lookup for a forwarded writable stream failed (for example a run metadata read that timed out) before anything was written to that stream. The lookup now starts on the first write, and a failure rejects that stream instead.
@TooTallNate - Serialize a
DataViewas the bytes it views. It previously fell through to devalue's built-in encoding, which persists the whole backingArrayBuffer— for a view onto Node's pooledBufferallocator, unrelated process memory.@TooTallNate - Retry the max-deliveries
run_failed/step_failedwrite and the step handler's workflow re-queue through queue redelivery when they fail transiently (429, 5xx, transport) instead of acking and leaving the run stuckrunning.
@workflow/core@4.8.10
@pranaygp - Fix an unhandled rejection that could exit the process when the encryption-key lookup for a forwarded writable stream failed (for example a run metadata read that timed out) before anything was written to that stream. The lookup now starts on the first write, and a failure rejects that stream instead.
@TooTallNate - Serialize a
DataViewas the bytes it views. It previously fell through to devalue's built-in encoding, which persists the whole backingArrayBuffer— for a view onto Node's pooledBufferallocator, unrelated process memory.@TooTallNate - Retry the max-deliveries
run_failed/step_failedwrite and the step handler's workflow re-queue through queue redelivery when they fail transiently (429, 5xx, transport) instead of acking and leaving the run stuckrunning.@pranaygp -
start()with an explicitdeploymentId(and sorecreateRunFromExisting, i.e. Replay Run) no longer fails in a process that is not itself a deployment; it takes the cross-deployment path instead.@pranaygp - Mark
WorkflowRunFailedErrorandWorkflowRunCancelledErroras non-retryable, and makeFatalError.is()honor thefatalmarker, so a step that reads a terminal run'sreturnValuefails on its first attempt with the error intact instead of exhausting its retry budget first.@karthikscale3 - Route unrecognized backend connection and stream failures through existing retry policies, rebuilding shared event connections after repeated HTTP/2 failures. Keep invalid backend URLs, blocked ports, and unsupported request headers out of those retries. Include error cause chains in run-failure logs to expose underlying socket, DNS, and TLS failures.
@workflow/cli@4.3.14
@TooTallNate - Add a
DataViewreviver so run payloads containing one still hydrate in the observability UI and CLI. </content>
@workflow/world-local@4.4.2
@pranaygp - Upgrade undici to 7.30.0, which stops a failed HTTP/2 stream from leaving a phantom in-flight request on its connection.
@workflow/world-postgres@4.3.8
@pranaygp - Upgrade undici to 7.30.0, which stops a failed HTTP/2 stream from leaving a phantom in-flight request on its connection.
@workflow/world-vercel@4.7.5
@pranaygp - Send event requests with bodies too large to re-buffer over HTTP/1.1 so they no longer stall behind in-flight HTTP/2 streams, and retry event-log reads whose HTTP/2 stream the peer reset.
@karthikscale3 - Route unrecognized backend connection and stream failures through existing retry policies, rebuilding shared event connections after repeated HTTP/2 failures. Keep invalid backend URLs, blocked ports, and unsupported request headers out of those retries. Include error cause chains in run-failure logs to expose underlying socket, DNS, and TLS failures.
@pranaygp - Upgrade undici to 7.30.0, which stops a failed HTTP/2 stream from leaving a phantom in-flight request on its connection.
@workflow/web-shared@4.1.26
@TooTallNate - Add a
DataViewreviver so run payloads containing one still hydrate in the observability UI and CLI. </content>
@workflow/next@4.1.14
@pranaygp - Fix
next devsometimes running the workflow build (and starting a second watcher) twice on startup when Next.js resetsprocess.envbetweennext.configevaluations
@workflow/errors@4.2.2
@pranaygp - Mark
WorkflowRunFailedErrorandWorkflowRunCancelledErroras non-retryable, and makeFatalError.is()honor thefatalmarker, so a step that reads a terminal run'sreturnValuefails on its first attempt with the error intact instead of exhausting its retry budget first.
4.8.9
15 Sep 2026- security
@Rich-Harris - Bump
devalueto 5.9.2 to address published security advisory. @ctgowrie - Stop framed stream reconnects after the consumer cancels, including while completion checks or reconnect acquisition are still pending.
@torsello - Allow parentheses and square brackets in workflow names
@workflow/core@4.8.9
@ctgowrie - Stop framed stream reconnects after the consumer cancels, including while completion checks or reconnect acquisition are still pending.
- security
@Rich-Harris - Bump
devalueto 5.9.2 to address published security advisory. @torsello - Allow parentheses and square brackets in workflow names
@workflow/world-postgres@4.3.7
@komly - Fix run creation and slot allocation not happening transactionally
@komly - Queue deliveries no longer inherit
fetch's 300s headers/body deadlines, which redelivered healthy long-running inline work while it was still executing. Deadlines can be set withWORKFLOW_POSTGRES_HEADERS_TIMEOUT_MSandWORKFLOW_POSTGRES_BODY_TIMEOUT_MS.@komly - Preserve error names, messages, stacks, and nested causes in Graphile Worker log metadata.
@komly - Fix stream cursors not being advanced for skipped chunks, so repeated notifications cannot consume the requested start offset twice
@VaguelySerious - Fix stream readers leaking EventEmitter listeners on EOF, initial query failure, and World close, and fail pending readers when the World is closed.
@himself65 - Fix
readFromStreamerroring when rows were written after the stream's first EOF marker. Rows past the first EOF are now ignored consistently acrossreadFromStream(),getStreamChunks(), andgetStreamInfo().
@workflow/builders@4.1.14
@TooTallNate - Deduplicate identical pnpm peer-variant copies of a package instead of failing the build with a duplicate step/workflow ID error. Different implementations that map to the same ID still fail.
4.8.8
9 Sep 2026@VaguelySerious - Honor
WORKFLOW_NODE_HTTPon the queue client's transport, so a deployment whose bundled undici is unusable can still acknowledge queue messages instead of redelivering them until the invocation is killed.@VaguelySerious - Give the queue client its own connection pool and a total per-request deadline, so a stalled queue acknowledgement fails fast instead of holding the invocation until the platform kills it.
4.8.7
9 Sep 2026@karthikscale3 - Classify HTTP/2 response stream timeouts as retryable transport failures so
start()can return the queued run through resilient start.
4.8.6
9 Sep 2026@AndrewBarba - Abort active local queue deliveries when the World closes, including when transport timeouts are disabled.
@karthikscale3 - Recycle the events HTTP/2 connection pool when a streamed response body fails after its headers arrive.
@VaguelySerious - Authenticate
deploymentId: "latest"with the deployment's own OIDC identity instead of an ambientVERCEL_TOKEN, and scope the request to the configured team, fixing spurious 404s when resolving the latest deployment
@workflow/world-local@4.4.1
@AndrewBarba - Abort active local queue deliveries when the World closes, including when transport timeouts are disabled.
@workflow/world-vercel@4.7.2
@karthikscale3 - Recycle the events HTTP/2 connection pool when a streamed response body fails after its headers arrive.
@VaguelySerious - Authenticate
deploymentId: "latest"with the deployment's own OIDC identity instead of an ambientVERCEL_TOKEN, and scope the request to the configured team, fixing spurious 404s when resolving the latest deployment
@workflow/web-shared@4.1.22
@karthikscale3 - Prevent event payload flicker during decryption.
@workflow/swc-plugin@4.1.3
@TooTallNate - Register class expressions through an IIFE that closes over the class instead of module-level code that references it by name, fixing the unresolvable
AnonymousClassreference emitted for shapes such asvar Foo = class { ... }in pre-bundled packages.
@workflow/next@4.1.10
@NathanColosimo - Discover workflows imported by every Next.js file convention entrypoint.
4.8.5
25 Aug 2026@TooTallNate - Step-argument serialization failures now fail the step with a catchable error (via a
step_failedevent, like a step-body failure) instead of failing the run from outside the workflow, and when uncaught they fail the run immediately as aUSER_ERRORrather than retrying until max queue deliveries.@AndrewBarba - Preserve event-log order when adjacent hook payloads resume independent workflow branches.
@VaguelySerious - Fix a replay-determinism gap where branch wake order — and therefore step correlation ids — could depend on how much of the event log an invocation had loaded, corrupting runs under concurrent replays (CORRUPTED_EVENT_LOG).
@workflow/core@4.8.5
@TooTallNate - Step-argument serialization failures now fail the step with a catchable error (via a
step_failedevent, like a step-body failure) instead of failing the run from outside the workflow, and when uncaught they fail the run immediately as aUSER_ERRORrather than retrying until max queue deliveries.@AndrewBarba - Preserve event-log order when adjacent hook payloads resume independent workflow branches.
@VaguelySerious - Fix a replay-determinism gap where branch wake order — and therefore step correlation ids — could depend on how much of the event log an invocation had loaded, corrupting runs under concurrent replays (CORRUPTED_EVENT_LOG).
@alangenfeld - Verify stream tail when the reconnecting framed reader receives an EOF to ensure it is due to stream completion (and not other world-side behavior)
@workflow/world@4.5.0
@karthikscale3 - Accept and send an optional
runIdwhen listing events by correlation ID@ctgowrie - Use the active queue namespace when re-enqueuing workflow runs during world startup recovery.
@workflow/world-local@4.4.0
@karthikscale3 - Add
WORKFLOW_LOCAL_RECOVER_ACTIVE_RUNSenv var as a fallback for therecoverActiveRunsoption, so re-enqueueing of pending/running runs on startup can be disabled without a custom world module.@ctgowrie - Use the active queue namespace when re-enqueuing workflow runs during world startup recovery.
@pranaygp - Reduce local sequential-step replay I/O with bounded recent-event and storage-directory caches.
@karthikscale3 - Accept and send an optional
runIdwhen listing events by correlation ID
@workflow/world-postgres@4.3.5
@shin4141 - Commit step entities and their
step_createdevents atomically.@ctgowrie - Use the active queue namespace when re-enqueuing workflow runs during world startup recovery.
@joeyhotz - Throw
EntityConflictErrorwhen arun_createdevent targets a run that already exists, instead of resolving with no run. This matchesworld-localandworld-vercel, and stopsstart()from throwingMissing 'run' in server response for 'run_created' eventwhen the resilient start path wins the race.@karthikscale3 - Accept and send an optional
runIdwhen listing events by correlation ID
@workflow/world-vercel@4.7.1
@karthikscale3 - Accept and send an optional
runIdwhen listing events by correlation ID
@workflow/web@4.1.21
@karthikscale3 - Accept and send an optional
runIdwhen listing events by correlation ID
@workflow/ai@4.2.1
@VaguelySerious - Reduce DurableAgent step boundary payload by reconstructing
StepResultoutside the step instead of inside.
@workflow/nitro@4.1.11
@NathanColosimo - Clean up temporary Nitro Vite servers and Workflow build contexts after builds.
4.8.4
19 Aug 2026@VaguelySerious - Add
WORKFLOW_NODE_HTTPto run the Vercel and Local World HTTP layers on Node's built-innode:httpandnode:httpsmodules instead of their usual HTTP client library.@VaguelySerious - Add
WORKFLOW_NODE_HTTPto run the Vercel and Local World HTTP layers on Node's built-innode:httpandnode:httpsmodules instead of their usual HTTP client library.@VaguelySerious - Reject a hook resume that races the hook's disposal, instead of journaling it after
hook_disposedand corrupting the owning run's event log.
@workflow/world@4.4.0
@VaguelySerious - Add
WORKFLOW_NODE_HTTPto run the Vercel and Local World HTTP layers on Node's built-innode:httpandnode:httpsmodules instead of their usual HTTP client library.
@workflow/world-local@4.3.0
@VaguelySerious - Add
WORKFLOW_NODE_HTTPto run the Vercel and Local World HTTP layers on Node's built-innode:httpandnode:httpsmodules instead of their usual HTTP client library.
@workflow/world-postgres@4.3.4
@VaguelySerious - Reject a hook resume that races the hook's disposal, instead of journaling it after
hook_disposedand corrupting the owning run's event log.
@workflow/world-vercel@4.7.0
@VaguelySerious - Add
WORKFLOW_NODE_HTTPto run the Vercel and Local World HTTP layers on Node's built-innode:httpandnode:httpsmodules instead of their usual HTTP client library.
4.8.3
14 Aug 2026@dummdidumm - Load SvelteKit route configuration through
@sveltejs/load-config, including projects that configure SvelteKit exclusively invite.config.
4.8.2
11 Aug 2026@ar-tama - Fix
Datesubclassing inside workflow functions. The deterministicDateoverride in the workflow VM now forwardsnew.targetviaReflect.construct, so subclasses likeTZDatefrom@date-fns/tzkeep their identity, methods, and fields. CallingDate()withoutnewnow returns the (fixed) time string per spec, instead of aDateobject.@pranaygp - Deliver step results, wait completions and hook payloads in strict event-log order relative to one another, preventing replay divergence (
CORRUPTED_EVENT_LOG) when a step completion is adjacent in the log to await_completedorhook_receivedthat a concurrent branch is awaiting.@VaguelySerious - Stop reporting replay divergence for an event the workflow is still on its way to consuming, by waiting for in-flight step and hook deliveries instead of a fixed delay
4.8.1
7 Aug 20264.8.0
31 Jul 2026@shalabhc - Enforce a server-supplied per-run event limit (default 25K)
@shalabhc - Enforce a server-supplied per-run event limit (default 25K)
@AndrewBarba - Retry transient
EPERMunlink failures on Windows when deleting hook and entity JSON files. A concurrent reader briefly holding a file open madedeleteJSONthrow a share-violationEPERM, which surfaced as a failed operation — for example a failedrun.cancel()whiledeleteAllHooksForRunraced hook polling.
@workflow/core@4.8.0
@shalabhc - Enforce a server-supplied per-run event limit (default 25K)
@workflow/world@4.3.1
@shalabhc - Enforce a server-supplied per-run event limit (default 25K)
@workflow/world-local@4.2.4
@AndrewBarba - Retry transient
EPERMunlink failures on Windows when deleting hook and entity JSON files. A concurrent reader briefly holding a file open madedeleteJSONthrow a share-violationEPERM, which surfaced as a failed operation — for example a failedrun.cancel()whiledeleteAllHooksForRunraced hook polling.
@workflow/world-vercel@4.6.1
@VaguelySerious - Cancel the v4 event frame stream when a reader stops early, so the response body's undici connection returns to the pool instead of leaking.
@shalabhc - Enforce a server-supplied per-run event limit (default 25K)
@VaguelySerious - Event-log requests now multiplex over a single HTTP/2 connection instead of opening one connection per in-flight request
@VaguelySerious - Raise HTTP/2 receive windows on the events client, slightly decreasing read latency
@VaguelySerious - Retry stream close on retriable 5xx. Close is idempotent on the server (unlike chunk appends, which keep their no-5xx retry policy), and the server may return retriable 503s expecting the writer to close again.
@VaguelySerious - Enable HTTP/2 for the events API and stream write requests.
@VaguelySerious - Unify HTTP request handling into a shared core, extending OTEL client spans, trace-context propagation, and
DEBUGlogging to the v4 events, stream, and Vercel API request paths
@workflow/web@4.1.16
@VaguelySerious - Fix run and event observability pages hanging (~16s) and showing no data in the bundled server build, caused by HTTP/2 requests failing to reach
node:http2.
@workflow/nitro@4.1.6
@VaguelySerious - Fix HTTP/2 requests failing in production builds (Vite/Nitro, TanStack Start) where undici's bundled
node:http2could not load and fell back to a stub.
@workflow/sveltekit@4.0.15
@VaguelySerious - Fix HTTP/2 requests failing in SvelteKit production builds (undici's bundled
node:http2could not load, falling back to a stub).
@workflow/errors@4.2.1
@shalabhc - Enforce a server-supplied per-run event limit (default 25K)
4.7.0
29 Jul 2026@VaguelySerious - Add an optimistic-concurrency guard for event creation (on by default; opt out with
WORKFLOW_PRECONDITION_GUARD=0): replay-context event creations send astateUpdatedAtsnapshot timestamp, and the runtime reloads the event log and retries (then falls back to a fresh re-invocation) when the backend reports a newer out-of-band event with a 412PreconditionFailedError. Backends without guard support ignore the snapshot, so this is backward-compatible and fails open.@VaguelySerious - Add an optimistic-concurrency guard for event creation (on by default; opt out with
WORKFLOW_PRECONDITION_GUARD=0): replay-context event creations send astateUpdatedAtsnapshot timestamp, and the runtime reloads the event log and retries (then falls back to a fresh re-invocation) when the backend reports a newer out-of-band event with a 412PreconditionFailedError. Backends without guard support ignore the snapshot, so this is backward-compatible and fails open.@VaguelySerious - Add an optimistic-concurrency guard for event creation (on by default; opt out with
WORKFLOW_PRECONDITION_GUARD=0): replay-context event creations send astateUpdatedAtsnapshot timestamp, and the runtime reloads the event log and retries (then falls back to a fresh re-invocation) when the backend reports a newer out-of-band event with a 412PreconditionFailedError. Backends without guard support ignore the snapshot, so this is backward-compatible and fails open.
@workflow/core@4.7.0
@VaguelySerious - Add an optimistic-concurrency guard for event creation (on by default; opt out with
WORKFLOW_PRECONDITION_GUARD=0): replay-context event creations send astateUpdatedAtsnapshot timestamp, and the runtime reloads the event log and retries (then falls back to a fresh re-invocation) when the backend reports a newer out-of-band event with a 412PreconditionFailedError. Backends without guard support ignore the snapshot, so this is backward-compatible and fails open.
@workflow/world@4.3.0
@VaguelySerious - Add an optimistic-concurrency guard for event creation (on by default; opt out with
WORKFLOW_PRECONDITION_GUARD=0): replay-context event creations send astateUpdatedAtsnapshot timestamp, and the runtime reloads the event log and retries (then falls back to a fresh re-invocation) when the backend reports a newer out-of-band event with a 412PreconditionFailedError. Backends without guard support ignore the snapshot, so this is backward-compatible and fails open.
@workflow/world-vercel@4.6.0
@VaguelySerious - Add an optimistic-concurrency guard for event creation (on by default; opt out with
WORKFLOW_PRECONDITION_GUARD=0): replay-context event creations send astateUpdatedAtsnapshot timestamp, and the runtime reloads the event log and retries (then falls back to a fresh re-invocation) when the backend reports a newer out-of-band event with a 412PreconditionFailedError. Backends without guard support ignore the snapshot, so this is backward-compatible and fails open.@VaguelySerious - Bound HTTP requests with a 30s headers/body timeout instead of undici's 5-minute default, and retry transport timeouts and other transient failures in-process, so a stalled connection no longer leaves a run silently paused for ~5 minutes until the queue redelivers it. Override with
WORKFLOW_VERCEL_HEADERS_TIMEOUT_MS/WORKFLOW_VERCEL_BODY_TIMEOUT_MS.
@workflow/world-testing@4.1.14
@VaguelySerious - Fix flaky Local World tests caused by concurrent test servers sharing one data directory and re-enqueuing each other's in-flight runs on startup
@workflow/sveltekit@4.0.14
@TooTallNate - The generated webhook route now passes the incoming request directly to its handler instead of copying it into a new
Request, so the request body is no longer read before the handler runs.
@workflow/astro@4.0.14
@TooTallNate - The generated webhook route now passes the incoming request directly to its handler instead of copying it into a new
Request, so the request body is no longer read before the handler runs.
@workflow/errors@4.2.0
@VaguelySerious - Add an optimistic-concurrency guard for event creation (on by default; opt out with
WORKFLOW_PRECONDITION_GUARD=0): replay-context event creations send astateUpdatedAtsnapshot timestamp, and the runtime reloads the event log and retries (then falls back to a fresh re-invocation) when the backend reports a newer out-of-band event with a 412PreconditionFailedError. Backends without guard support ignore the snapshot, so this is backward-compatible and fails open.
4.6.2
24 Jul 2026- security
@TooTallNate - Update
nextdevDependency to 16.2.11 to address CVE-2026-64641
4.6.1
22 Jul 2026Reading the note…
4.6.0
6 Jul 2026Reading the note…
4.5.0
15 Jun 2026Reading the note…
4.4.0
11 Jun 2026Reading the note…
4.3.1
2 Jun 2026Reading the note…
4.3.0
2 Jun 2026Reading the note…
4.2.6
31 May 2026Reading the note…
4.2.5
22 May 2026Reading the note…
4.2.4
17 Apr 2026Reading the note…
4.2.3
16 Apr 2026Reading the note…
4.2.2
14 Apr 2026Reading the note…
4.2.1
11 Apr 2026Reading the note…
4.2.0
7 Apr 2026Reading the note…
- 2.x
2.0.6
26 Sep 2018Reading the note…
2.0.5
26 Sep 2018Reading the note…
2.0.4
26 Sep 2018Reading the note…
2.0.3
25 Sep 2018Reading the note…
2.0.2
25 Sep 2018Reading the note…
2.0.1
25 Sep 2018Reading the note…
2.0.0
18 Sep 2018Reading the note…
- 1.x
1.1.6
18 Sep 2018Reading the note…
1.1.5
12 Sep 2018Reading the note…
1.1.4
11 Sep 2018Reading the note…
1.1.3
11 Sep 2018Reading the note…
1.1.2
11 Sep 2018Reading the note…
1.1.1
10 Sep 2018Reading the note…