Skip to content

Add stateUpdatedAt precondition guard to event creation#2266

Draft
VaguelySerious wants to merge 4 commits into
mainfrom
peter/event-precondition-guard
Draft

Add stateUpdatedAt precondition guard to event creation#2266
VaguelySerious wants to merge 4 commits into
mainfrom
peter/event-precondition-guard

Conversation

@VaguelySerious
Copy link
Copy Markdown
Member

Summary

Adds an optimistic-concurrency guard so a replaying runtime working from a stale event-log snapshot can't advance a run past an event it hasn't seen (e.g. complete the run, or go to sleep, while a hook it should observe just arrived).

Replay-context event creations now send stateUpdatedAt — the ULID time (epoch ms) of the latest event the runtime has loaded. The backend records the time of the most recent out-of-band event per run and rejects a create whose snapshot predates it with 412. On rejection the runtime:

  1. Reloads the event log to completion from its cursor, merges it into the in-memory snapshot, and retries — up to twice.
  2. If still stale, rethrows so the run falls back to a queue re-invocation with a fresh replay.

Changes

  • @workflow/worldCreateEventParams.stateUpdatedAt.
  • @workflow/errorsPreconditionFailedError (HTTP 412).
  • @workflow/world-vercel — sends stateUpdatedAt in the event body; maps a 412 response to PreconditionFailedError.
  • @workflow/corelatestEventStateUpdatedAt + withPreconditionRetry helpers; guards wait_completed / run_completed in the replay loop and every create inside handleSuspension (per-create, so a retry never re-issues an already-created event); rethrows exhausted precondition errors so the queue re-invokes the flow route.

Tests

  • helpers.test.tslatestEventStateUpdatedAt (prefix-stripping ULID decode) and withPreconditionRetry (reload+retry, exhaustion rethrow, non-precondition passthrough).
  • world-vercel — 412 → PreconditionFailedError mapping; stateUpdatedAt is present/absent in the wire body as expected.

Server side

Pairs with the backend change that records the per-run latest-outside-event marker (bumped on externally-originated hook_received / step_completed) and returns 412. Fully backward-compatible: with an older backend, stateUpdatedAt is ignored and the guard is inert.

🤖 Generated with Claude Code

Replay-context event creations now send `stateUpdatedAt` — the ULID time
of the latest event the runtime has loaded — so the backend can reject a
create whose snapshot predates a newer out-of-band event (a received hook
or a completed step) with 412.

On a 412 (`PreconditionFailedError`) the runtime reloads the event log to
completion from its cursor, retries up to twice, then rethrows so the run
falls back to a queue re-invocation with a fresh replay.

- @workflow/world: CreateEventParams.stateUpdatedAt
- @workflow/errors: PreconditionFailedError (412)
- @workflow/world-vercel: send stateUpdatedAt; map 412 -> PreconditionFailedError
- @workflow/core: latestEventStateUpdatedAt + withPreconditionRetry helpers;
  guard wait_completed / run_completed and every suspension-handler create;
  rethrow exhausted precondition errors to the queue

Pairs with the workflow-server change that records the per-run
latest-outside-event marker and returns 412.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 5, 2026

🦋 Changeset detected

Latest commit: a7286e4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 22 packages
Name Type
@workflow/core Minor
@workflow/world-vercel Minor
@workflow/world Minor
@workflow/errors Minor
@workflow/builders Patch
@workflow/cli Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/vitest Patch
@workflow/web-shared Patch
@workflow/web Patch
workflow Minor
@workflow/world-testing Patch
@workflow/world-local Patch
@workflow/world-postgres Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/nuxt Patch
@workflow/ai Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Jun 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment Jun 5, 2026 4:19pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Jun 5, 2026 4:19pm
example-workflow Ready Ready Preview, Comment Jun 5, 2026 4:19pm
workbench-astro-workflow Ready Ready Preview, Comment Jun 5, 2026 4:19pm
workbench-express-workflow Ready Ready Preview, Comment Jun 5, 2026 4:19pm
workbench-fastify-workflow Ready Ready Preview, Comment Jun 5, 2026 4:19pm
workbench-hono-workflow Ready Ready Preview, Comment Jun 5, 2026 4:19pm
workbench-nitro-workflow Ready Ready Preview, Comment Jun 5, 2026 4:19pm
workbench-nuxt-workflow Ready Ready Preview, Comment Jun 5, 2026 4:19pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Jun 5, 2026 4:19pm
workbench-tanstack-start-workflow Ready Ready Preview, Comment Jun 5, 2026 4:19pm
workbench-vite-workflow Ready Ready Preview, Comment Jun 5, 2026 4:19pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Jun 5, 2026 4:19pm
workflow-swc-playground Ready Ready Preview, Comment Jun 5, 2026 4:19pm
workflow-tarballs Ready Ready Preview, Comment Jun 5, 2026 4:19pm
workflow-web Ready Ready Preview, Comment Jun 5, 2026 4:19pm

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 5, 2026

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 0.043s (~) 1.006s (~) 0.963s 10 1.00x
💻 Local Nitro 0.046s (+7.5% 🔺) 1.006s (~) 0.960s 10 1.06x
💻 Local Next.js (Turbopack) 0.059s (+6.4% 🔺) 1.005s (~) 0.947s 10 1.35x
🐘 Postgres Nitro 0.062s (-2.4%) 1.012s (~) 0.950s 10 1.42x
🐘 Postgres Express 0.065s (-2.3%) 1.013s (~) 0.948s 10 1.50x
🐘 Postgres Next.js (Turbopack) 0.068s (-3.4%) 1.011s (~) 0.944s 10 1.56x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 0.281s (+7.4% 🔺) 2.413s (-0.9%) 2.132s 10 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 1.089s (-1.1%) 2.006s (~) 0.917s 10 1.00x
🐘 Postgres Nitro 1.103s (-0.9%) 2.011s (~) 0.908s 10 1.01x
💻 Local Nitro 1.106s (+0.9%) 2.009s (~) 0.903s 10 1.02x
🐘 Postgres Express 1.110s (+1.6%) 2.009s (~) 0.900s 10 1.02x
💻 Local Next.js (Turbopack) 1.125s (+1.5%) 2.006s (~) 0.881s 10 1.03x
🐘 Postgres Next.js (Turbopack) 1.141s (~) 2.010s (~) 0.869s 10 1.05x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.704s (+8.8% 🔺) 3.701s (+4.9%) 1.998s 10 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 10.533s (~) 11.022s (~) 0.489s 3 1.00x
🐘 Postgres Nitro 10.565s (~) 11.023s (~) 0.458s 3 1.00x
💻 Local Nitro 10.581s (~) 11.025s (~) 0.443s 3 1.00x
🐘 Postgres Express 10.593s (-1.2%) 11.023s (-2.9%) 0.430s 3 1.01x
💻 Local Next.js (Turbopack) 10.744s (+1.3%) 11.023s (~) 0.279s 3 1.02x
🐘 Postgres Next.js (Turbopack) 10.901s (+0.8%) 11.018s (~) 0.117s 3 1.03x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 14.364s (+7.8% 🔺) 16.343s (+4.8%) 1.979s 2 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 13.782s (~) 14.022s (~) 0.240s 5 1.00x
💻 Local Express 13.792s (-0.8%) 14.027s (-1.4%) 0.234s 5 1.00x
💻 Local Nitro 13.805s (+0.6%) 14.027s (~) 0.223s 5 1.00x
🐘 Postgres Express 13.941s (+0.8%) 14.225s (~) 0.285s 5 1.01x
💻 Local Next.js (Turbopack) 14.344s (+2.3%) 15.029s (+2.8%) 0.685s 4 1.04x
🐘 Postgres Next.js (Turbopack) 14.723s (+0.9%) 15.271s (+1.7%) 0.548s 4 1.07x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 21.571s (-15.5% 🟢) 23.490s (-14.9% 🟢) 1.919s 3 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 12.409s (-2.6%) 13.017s (~) 0.608s 7 1.00x
💻 Local Express 12.462s (-1.1%) 13.024s (~) 0.562s 7 1.00x
💻 Local Nitro 12.572s (+1.3%) 13.028s (~) 0.456s 7 1.01x
🐘 Postgres Express 12.623s (-0.9%) 13.019s (-2.2%) 0.397s 7 1.02x
💻 Local Next.js (Turbopack) 13.620s (+5.2% 🔺) 14.026s (+5.4% 🔺) 0.406s 7 1.10x
🐘 Postgres Next.js (Turbopack) 13.810s (~) 14.019s (~) 0.208s 7 1.11x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 30.038s (+3.4%) 32.550s (+3.7%) 2.512s 3 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.182s (-1.4%) 2.008s (~) 0.826s 15 1.00x
🐘 Postgres Express 1.186s (-1.1%) 2.008s (-0.7%) 0.821s 15 1.00x
💻 Local Express 1.199s (-5.1% 🟢) 2.006s (~) 0.807s 15 1.01x
🐘 Postgres Next.js (Turbopack) 1.240s (-0.9%) 2.008s (~) 0.768s 15 1.05x
💻 Local Nitro 1.350s (+11.2% 🔺) 2.006s (~) 0.657s 15 1.14x
💻 Local Next.js (Turbopack) 1.357s (+11.0% 🔺) 2.006s (~) 0.649s 15 1.15x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.660s (-4.0%) 4.276s (-1.5%) 1.616s 8 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.241s (-1.4%) 2.007s (~) 0.766s 15 1.00x
🐘 Postgres Express 1.254s (-5.4% 🟢) 2.007s (~) 0.753s 15 1.01x
🐘 Postgres Next.js (Turbopack) 1.392s (-1.3%) 2.008s (~) 0.617s 15 1.12x
💻 Local Next.js (Turbopack) 1.719s (~) 2.149s (+7.1% 🔺) 0.430s 14 1.38x
💻 Local Express 1.753s (-6.9% 🟢) 2.005s (-9.7% 🟢) 0.252s 15 1.41x
💻 Local Nitro 1.954s (+14.7% 🔺) 2.392s (+15.4% 🔺) 0.438s 13 1.57x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 4.505s (+10.7% 🔺) 6.313s (+1.7%) 1.808s 5 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.396s (~) 2.008s (~) 0.612s 15 1.00x
🐘 Postgres Nitro 1.399s (-0.5%) 2.007s (~) 0.608s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.767s (~) 2.318s (+1.0%) 0.551s 13 1.27x
💻 Local Next.js (Turbopack) 4.335s (-1.4%) 5.012s (~) 0.677s 6 3.11x
💻 Local Express 5.059s (-4.5%) 5.514s (-8.3% 🟢) 0.455s 6 3.62x
💻 Local Nitro 6.647s (+40.9% 🔺) 7.217s (+44.0% 🔺) 0.569s 5 4.76x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 6.857s (+1.8%) 9.219s (+5.9% 🔺) 2.362s 4 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.177s (-1.9%) 2.007s (~) 0.830s 15 1.00x
🐘 Postgres Express 1.189s (-1.2%) 2.008s (~) 0.819s 15 1.01x
🐘 Postgres Next.js (Turbopack) 1.245s (-1.6%) 2.007s (~) 0.762s 15 1.06x
💻 Local Next.js (Turbopack) 1.347s (+3.2%) 2.006s (~) 0.658s 15 1.14x
💻 Local Nitro 1.568s (+1.6%) 2.007s (~) 0.439s 15 1.33x
💻 Local Express 1.586s (-4.2%) 2.006s (-3.3%) 0.420s 15 1.35x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.514s (-5.9% 🟢) 3.981s (-8.1% 🟢) 1.467s 8 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.248s (-2.1%) 2.009s (~) 0.761s 15 1.00x
🐘 Postgres Express 1.266s (+1.8%) 2.008s (~) 0.743s 15 1.01x
🐘 Postgres Next.js (Turbopack) 1.426s (+2.1%) 2.008s (~) 0.582s 15 1.14x
💻 Local Next.js (Turbopack) 2.008s (-0.6%) 2.592s (+3.4%) 0.584s 12 1.61x
💻 Local Express 2.049s (-3.6%) 2.592s (~) 0.543s 12 1.64x
💻 Local Nitro 2.127s (+21.1% 🔺) 2.591s (+25.0% 🔺) 0.464s 12 1.71x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.889s (-5.0% 🟢) 5.618s (-6.4% 🟢) 1.730s 6 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.371s (-8.6% 🟢) 2.008s (-16.2% 🟢) 0.637s 15 1.00x
🐘 Postgres Nitro 1.382s (-5.5% 🟢) 2.009s (~) 0.627s 15 1.01x
🐘 Postgres Next.js (Turbopack) 1.740s (+5.1% 🔺) 2.075s (~) 0.335s 15 1.27x
💻 Local Next.js (Turbopack) 5.178s (~) 5.681s (-5.6% 🟢) 0.503s 6 3.78x
💻 Local Express 5.767s (-1.6%) 6.216s (~) 0.448s 5 4.21x
💻 Local Nitro 6.378s (+34.5% 🔺) 6.815s (+27.5% 🔺) 0.437s 5 4.65x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.073s (+2.9%) 7.175s (+6.5% 🔺) 2.102s 5 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.589s (-11.7% 🟢) 1.024s (-2.3%) 0.435s 59 1.00x
🐘 Postgres Nitro 0.592s (-2.8%) 1.041s (+3.3%) 0.449s 58 1.00x
💻 Local Express 0.593s (-9.6% 🟢) 1.005s (-3.4%) 0.412s 60 1.01x
💻 Local Nitro 0.610s (-24.2% 🟢) 1.005s (-16.6% 🟢) 0.395s 60 1.03x
💻 Local Next.js (Turbopack) 0.838s (+20.4% 🔺) 1.004s (~) 0.166s 60 1.42x
🐘 Postgres Next.js (Turbopack) 0.842s (+3.7%) 1.042s (+3.5%) 0.199s 58 1.43x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.655s (-24.2% 🟢) 7.461s (-21.6% 🟢) 1.807s 9 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.315s (-7.2% 🟢) 2.030s (+1.1%) 0.716s 45 1.00x
🐘 Postgres Express 1.414s (-8.0% 🟢) 2.008s (-6.7% 🟢) 0.594s 45 1.08x
💻 Local Express 1.538s (-3.4%) 2.028s (~) 0.491s 45 1.17x
💻 Local Nitro 1.684s (+15.1% 🔺) 2.123s (+5.9% 🔺) 0.439s 43 1.28x
🐘 Postgres Next.js (Turbopack) 2.039s (+4.9%) 2.508s (+15.4% 🔺) 0.470s 36 1.55x
💻 Local Next.js (Turbopack) 2.055s (+18.9% 🔺) 2.852s (+40.6% 🔺) 0.797s 32 1.56x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 14.745s (+11.5% 🔺) 16.895s (+9.1% 🔺) 2.150s 6 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 2.599s (-10.8% 🟢) 3.034s (-7.5% 🟢) 0.436s 40 1.00x
🐘 Postgres Express 2.830s (-2.8%) 3.194s (-9.8% 🟢) 0.363s 38 1.09x
💻 Local Express 3.234s (-5.4% 🟢) 4.009s (~) 0.775s 30 1.24x
💻 Local Nitro 3.325s (+3.7%) 4.009s (~) 0.684s 30 1.28x
🐘 Postgres Next.js (Turbopack) 3.924s (+1.7%) 4.148s (+1.8%) 0.225s 29 1.51x
💻 Local Next.js (Turbopack) 4.290s (+15.1% 🔺) 5.010s (+21.9% 🔺) 0.720s 24 1.65x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 27.335s (-8.2% 🟢) 30.214s (-5.5% 🟢) 2.878s 4 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.225s (-10.8% 🟢) 1.007s (~) 0.782s 60 1.00x
🐘 Postgres Nitro 0.234s (-0.8%) 1.023s (+1.6%) 0.789s 59 1.04x
🐘 Postgres Next.js (Turbopack) 0.284s (+0.8%) 1.007s (~) 0.722s 60 1.26x
💻 Local Nitro 0.513s (+20.9% 🔺) 1.096s (+9.1% 🔺) 0.582s 55 2.28x
💻 Local Express 0.551s (+18.7% 🔺) 1.096s (+9.1% 🔺) 0.544s 55 2.45x
💻 Local Next.js (Turbopack) 0.605s (-0.9%) 1.021s (-1.7%) 0.416s 59 2.69x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.735s (+16.6% 🔺) 4.516s (+4.2%) 1.780s 14 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.337s (-9.1% 🟢) 1.006s (~) 0.669s 90 1.00x
🐘 Postgres Express 0.361s (+6.5% 🔺) 1.006s (~) 0.645s 90 1.07x
🐘 Postgres Next.js (Turbopack) 0.497s (+2.9%) 1.006s (~) 0.510s 90 1.47x
💻 Local Express 2.081s (-5.0% 🟢) 2.608s (-7.6% 🟢) 0.527s 35 6.17x
💻 Local Nitro 2.181s (+5.7% 🔺) 2.767s (+4.9%) 0.586s 33 6.47x
💻 Local Next.js (Turbopack) 2.218s (-9.5% 🟢) 2.946s (-5.3% 🟢) 0.727s 31 6.58x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.973s (-86.4% 🟢) 7.851s (-82.9% 🟢) 1.878s 12 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.651s (-8.0% 🟢) 1.006s (~) 0.355s 120 1.00x
🐘 Postgres Express 0.701s (+12.0% 🔺) 1.006s (-4.6%) 0.305s 120 1.08x
🐘 Postgres Next.js (Turbopack) 1.000s (+2.1%) 1.762s (+9.6% 🔺) 0.762s 69 1.54x
💻 Local Express 9.704s (-5.9% 🟢) 10.276s (-5.4% 🟢) 0.572s 12 14.91x
💻 Local Nitro 9.942s (+14.1% 🔺) 10.613s (+13.7% 🔺) 0.672s 12 15.27x
💻 Local Next.js (Turbopack) 10.175s (-1.3%) 11.118s (-1.6%) 0.942s 11 15.63x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 21.041s (-93.2% 🟢) 23.180s (-92.6% 🟢) 2.138s 6 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 1.161s (-0.7%) 2.005s (~) 0.011s (-10.2% 🟢) 2.019s (~) 0.858s 10 1.00x
🐘 Postgres Nitro 1.164s (-1.4%) 2.002s (~) 0.001s (-28.6% 🟢) 2.010s (~) 0.846s 10 1.00x
💻 Local Nitro 1.170s (+1.7%) 2.006s (~) 0.013s (+29.0% 🔺) 2.021s (~) 0.850s 10 1.01x
🐘 Postgres Express 1.177s (-1.4%) 2.001s (~) 0.001s (-95.9% 🟢) 2.011s (-1.6%) 0.835s 10 1.01x
💻 Local Next.js (Turbopack) 1.199s (+2.6%) 2.003s (~) 0.011s (+5.0%) 2.018s (~) 0.819s 10 1.03x
🐘 Postgres Next.js (Turbopack) 1.264s (+2.4%) 2.002s (~) 0.001s (-13.3% 🟢) 2.011s (~) 0.747s 10 1.09x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.393s (+10.5% 🔺) 3.518s (+2.9%) 1.660s (+221.8% 🔺) 5.717s (+27.9% 🔺) 3.324s 10 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -

🔍 Observability: Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.571s (-5.6% 🟢) 2.007s (~) 0.004s (-1.7%) 2.025s (~) 0.454s 30 1.00x
💻 Local Express 1.578s (-3.6%) 2.010s (~) 0.010s (-6.0% 🟢) 2.022s (~) 0.444s 30 1.00x
🐘 Postgres Express 1.664s (-4.2%) 2.008s (-6.3% 🟢) 0.004s (+22.4% 🔺) 2.027s (-6.2% 🟢) 0.363s 30 1.06x
🐘 Postgres Next.js (Turbopack) 1.774s (+0.6%) 2.010s (~) 0.004s (+0.8%) 2.027s (~) 0.252s 30 1.13x
💻 Local Next.js (Turbopack) 1.997s (+21.8% 🔺) 2.045s (+1.8%) 0.010s (~) 2.245s (+11.0% 🔺) 0.247s 27 1.27x
💻 Local Nitro 2.013s (+15.6% 🔺) 2.010s (~) 0.010s (+12.0% 🔺) 2.423s (+10.2% 🔺) 0.409s 25 1.28x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.893s (+2.6%) 7.052s (-7.2% 🟢) 0.213s (-37.0% 🟢) 7.764s (-8.5% 🟢) 1.871s 8 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -

🔍 Observability: Nitro

10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.718s (-0.6%) 1.050s (+1.8%) 0.000s (-83.0% 🟢) 1.060s (+1.1%) 0.342s 57 1.00x
🐘 Postgres Express 0.733s (-25.0% 🟢) 1.069s (-25.1% 🟢) 0.000s (+25.0% 🔺) 1.079s (-25.5% 🟢) 0.346s 56 1.02x
🐘 Postgres Next.js (Turbopack) 0.841s (+1.0%) 1.092s (+1.9%) 0.000s (-100.0% 🟢) 1.099s (+1.8%) 0.258s 55 1.17x
💻 Local Express 1.398s (-5.7% 🟢) 2.013s (~) 0.000s (-40.0% 🟢) 2.015s (~) 0.616s 30 1.95x
💻 Local Nitro 1.417s (+4.8%) 2.014s (~) 0.000s (-60.0% 🟢) 2.016s (~) 0.598s 30 1.97x
💻 Local Next.js (Turbopack) 1.461s (+0.9%) 2.013s (~) 0.000s (+12.5% 🔺) 2.016s (~) 0.555s 30 2.03x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.755s (+4.3%) 4.975s (-1.9%) 0.000s (+Infinity% 🔺) 5.534s (-1.7%) 1.779s 12 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -

🔍 Observability: Nitro

fan-out fan-in 10 streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.358s (-3.7%) 2.068s (+3.6%) 0.000s (+Infinity% 🔺) 2.099s (+4.3%) 0.741s 29 1.00x
🐘 Postgres Express 1.405s (-13.0% 🟢) 1.997s (-11.4% 🟢) 0.000s (+Infinity% 🔺) 2.024s (-11.2% 🟢) 0.620s 30 1.03x
🐘 Postgres Next.js (Turbopack) 1.843s (+6.7% 🔺) 2.311s (+2.2%) 0.000s (NaN%) 2.328s (+2.6%) 0.486s 26 1.36x
💻 Local Next.js (Turbopack) 2.917s (-9.2% 🟢) 3.523s (-9.6% 🟢) 0.000s (+24.4% 🔺) 3.529s (-9.5% 🟢) 0.612s 18 2.15x
💻 Local Express 2.984s (-10.5% 🟢) 3.673s (-4.3%) 0.001s (+56.9% 🔺) 3.677s (-4.3%) 0.693s 17 2.20x
💻 Local Nitro 3.239s (+4.0%) 3.965s (+1.8%) 0.001s (+200.0% 🔺) 3.968s (+1.7%) 0.729s 16 2.39x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.443s (-22.7% 🟢) 7.190s (-16.1% 🟢) 0.000s (+Infinity% 🔺) 7.760s (-14.6% 🟢) 2.317s 8 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -

🔍 Observability: Nitro

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Express 14/21
🐘 Postgres Nitro 17/21
▲ Vercel Nitro 21/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 14/21
Next.js (Turbopack) 🐘 Postgres 14/21
Nitro 🐘 Postgres 20/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

  • Local: success
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 5, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
❌ ▲ Vercel Production 1241 25 219 1485
✅ 💻 Local Development 1671 0 219 1890
✅ 📦 Local Production 1671 0 219 1890
✅ 🐘 Local Postgres 1671 0 219 1890
✅ 🪟 Windows 135 0 0 135
❌ 📋 Other 767 2 176 945
Total 7156 27 1052 8235

❌ Failed Tests

▲ Vercel Production (25 failed)

astro (2 failed):

  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM

example (3 failed):

  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step | wrun_01KTC9GHVPANGP72NPCN05P7QA | 🔍 observability
  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM

express (2 failed):

  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM

fastify (2 failed):

  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM

hono (2 failed):

  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM

nextjs-turbopack (2 failed):

  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM

nextjs-webpack (2 failed):

  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM

nitro (2 failed):

  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM

nuxt (2 failed):

  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM

sveltekit (3 failed):

  • outputStreamWorkflow negative startIndex (reads from end)
  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM

vite (3 failed):

  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM
  • experimental_setAttributes Promise.all of disjoint-key writes: every key lands
📋 Other (2 failed)

e2e-vercel-prod-tanstack-start (2 failed):

  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM

Details by Category

❌ ▲ Vercel Production
App Passed Failed Skipped
❌ astro 107 2 26
❌ example 106 3 26
❌ express 107 2 26
❌ fastify 107 2 26
❌ hono 107 2 26
❌ nextjs-turbopack 131 2 2
❌ nextjs-webpack 131 2 2
❌ nitro 107 2 26
❌ nuxt 107 2 26
❌ sveltekit 125 3 7
❌ vite 106 3 26
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 110 0 25
✅ express-stable 110 0 25
✅ fastify-stable 110 0 25
✅ hono-stable 110 0 25
✅ nextjs-turbopack-canary 116 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 135 0 0
✅ nextjs-webpack-canary 116 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 135 0 0
✅ nitro-stable 110 0 25
✅ nuxt-stable 110 0 25
✅ sveltekit-stable 129 0 6
✅ vite-stable 110 0 25
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 110 0 25
✅ express-stable 110 0 25
✅ fastify-stable 110 0 25
✅ hono-stable 110 0 25
✅ nextjs-turbopack-canary 116 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 135 0 0
✅ nextjs-webpack-canary 116 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 135 0 0
✅ nitro-stable 110 0 25
✅ nuxt-stable 110 0 25
✅ sveltekit-stable 129 0 6
✅ vite-stable 110 0 25
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 110 0 25
✅ express-stable 110 0 25
✅ fastify-stable 110 0 25
✅ hono-stable 110 0 25
✅ nextjs-turbopack-canary 116 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 135 0 0
✅ nextjs-webpack-canary 116 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 135 0 0
✅ nitro-stable 110 0 25
✅ nuxt-stable 110 0 25
✅ sveltekit-stable 129 0 6
✅ vite-stable 110 0 25
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 135 0 0
❌ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 110 0 25
✅ e2e-local-dev-tanstack-start- 110 0 25
✅ e2e-local-postgres-nest-stable 110 0 25
✅ e2e-local-postgres-tanstack-start- 110 0 25
✅ e2e-local-prod-nest-stable 110 0 25
✅ e2e-local-prod-tanstack-start- 110 0 25
❌ e2e-vercel-prod-tanstack-start 107 2 26

📋 View full workflow run


Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: success
  • Local Prod: success
  • Local Postgres: success
  • Windows: success

Check the workflow run for details.

Temporary — run e2e against the workflow-server
peter/event-precondition-guard preview to exercise the 412 guard.
Reverted before merge / once the server side merges.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion-guard

# Conflicts:
#	packages/core/src/runtime/helpers.test.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant