Skip to content

[core] [world] Lazy run creation on start#1537

Merged
VaguelySerious merged 40 commits intomainfrom
peter/lazy-start
Apr 6, 2026
Merged

[core] [world] Lazy run creation on start#1537
VaguelySerious merged 40 commits intomainfrom
peter/lazy-start

Conversation

@VaguelySerious
Copy link
Copy Markdown
Member

@VaguelySerious VaguelySerious commented Mar 27, 2026

This PR makes start resilient: ensures that as long as the queue is up, the world storage layer being down will not affect run creations, only defer them.

This works by sending the run creation payload into the queue, and allowing the runtime to re-try creating the run it was invoked for.

See the live docs

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 27, 2026

🦋 Changeset detected

Latest commit: 90b9a7f

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

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

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 Mar 27, 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 Apr 6, 2026 6:57pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Apr 6, 2026 6:57pm
example-workflow Ready Ready Preview, Comment Apr 6, 2026 6:57pm
workbench-astro-workflow Ready Ready Preview, Comment Apr 6, 2026 6:57pm
workbench-express-workflow Ready Ready Preview, Comment Apr 6, 2026 6:57pm
workbench-fastify-workflow Ready Ready Preview, Comment Apr 6, 2026 6:57pm
workbench-hono-workflow Ready Ready Preview, Comment Apr 6, 2026 6:57pm
workbench-nitro-workflow Ready Ready Preview, Comment Apr 6, 2026 6:57pm
workbench-nuxt-workflow Ready Ready Preview, Comment Apr 6, 2026 6:57pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Apr 6, 2026 6:57pm
workbench-vite-workflow Ready Ready Preview, Comment Apr 6, 2026 6:57pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Apr 6, 2026 6:57pm
workflow-swc-playground Ready Ready Preview, Comment Apr 6, 2026 6:57pm

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 27, 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 🥇 Nitro 0.040s (-6.9% 🟢) 1.005s (~) 0.965s 10 1.00x
💻 Local Express 0.042s (+1.0%) 1.006s (~) 0.964s 10 1.04x
💻 Local Next.js (Turbopack) 0.045s 1.005s 0.960s 10 1.12x
🐘 Postgres Express 0.052s (-13.8% 🟢) 1.011s (~) 0.958s 10 1.31x
🌐 Redis Next.js (Turbopack) 0.054s 1.005s 0.951s 10 1.33x
🐘 Postgres Nitro 0.058s (-11.7% 🟢) 1.011s (~) 0.953s 10 1.43x
🐘 Postgres Next.js (Turbopack) 0.058s 1.010s 0.952s 10 1.45x
🌐 MongoDB Next.js (Turbopack) 0.095s 1.007s 0.912s 10 2.37x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 0.269s (-61.0% 🟢) 2.312s (-19.7% 🟢) 2.043s 10 1.00x
▲ Vercel Express 0.270s (-42.7% 🟢) 2.374s (-14.4% 🟢) 2.103s 10 1.01x
▲ Vercel Nitro 0.279s (-53.6% 🟢) 1.971s (-27.0% 🟢) 1.692s 10 1.04x

🔍 Observability: Next.js (Turbopack) | Express | Nitro

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 1.118s 2.006s 0.888s 10 1.00x
💻 Local Nitro 1.121s (-0.6%) 2.006s (~) 0.885s 10 1.00x
💻 Local Express 1.122s (-0.7%) 2.006s (~) 0.884s 10 1.00x
🌐 Redis Next.js (Turbopack) 1.125s 2.006s 0.881s 10 1.01x
🐘 Postgres Express 1.129s (-1.5%) 2.010s (~) 0.881s 10 1.01x
🐘 Postgres Next.js (Turbopack) 1.145s 2.011s 0.866s 10 1.02x
🐘 Postgres Nitro 1.150s (+0.6%) 2.016s (~) 0.866s 10 1.03x
🌐 MongoDB Next.js (Turbopack) 1.313s 2.008s 0.696s 10 1.17x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.767s (-20.4% 🟢) 3.356s (-16.2% 🟢) 1.589s 10 1.00x
▲ Vercel Express 1.852s (-21.2% 🟢) 3.642s (-9.2% 🟢) 1.790s 10 1.05x
▲ Vercel Next.js (Turbopack) 1.869s (-10.4% 🟢) 3.365s (-11.0% 🟢) 1.496s 10 1.06x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 10.713s (-1.5%) 11.020s (~) 0.307s 3 1.00x
💻 Local Next.js (Turbopack) 10.778s 11.023s 0.244s 3 1.01x
🌐 Redis Next.js (Turbopack) 10.827s 11.024s 0.196s 3 1.01x
🐘 Postgres Next.js (Turbopack) 10.834s 11.020s 0.187s 3 1.01x
🐘 Postgres Nitro 10.894s (~) 11.021s (~) 0.127s 3 1.02x
💻 Local Express 10.904s (~) 11.023s (~) 0.119s 3 1.02x
💻 Local Nitro 10.914s (~) 11.024s (~) 0.109s 3 1.02x
🌐 MongoDB Next.js (Turbopack) 12.308s 13.018s 0.710s 3 1.15x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 16.843s (+4.4%) 18.263s (+1.0%) 1.419s 2 1.00x
▲ Vercel Nitro 16.939s (+1.4%) 18.538s (+0.8%) 1.599s 2 1.01x
▲ Vercel Next.js (Turbopack) 17.703s (+5.7% 🔺) 18.802s (+2.6%) 1.099s 2 1.05x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 14.123s (-3.3%) 15.024s (~) 0.900s 4 1.00x
🌐 Redis Next.js (Turbopack) 14.236s 15.029s 0.792s 4 1.01x
🐘 Postgres Nitro 14.480s (~) 15.022s (~) 0.543s 4 1.03x
🐘 Postgres Next.js (Turbopack) 14.483s 15.024s 0.540s 4 1.03x
💻 Local Next.js (Turbopack) 14.653s 15.028s 0.374s 4 1.04x
💻 Local Express 14.884s (~) 15.029s (~) 0.145s 4 1.05x
💻 Local Nitro 14.910s (-0.6%) 15.029s (-1.6%) 0.119s 4 1.06x
🌐 MongoDB Next.js (Turbopack) 17.890s 18.026s 0.136s 4 1.27x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 31.596s (+3.9%) 33.028s (~) 1.433s 2 1.00x
▲ Vercel Express 31.793s (+4.0%) 33.273s (+3.3%) 1.480s 2 1.01x
▲ Vercel Next.js (Turbopack) 31.846s (+1.4%) 33.397s (~) 1.550s 2 1.01x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 13.316s 14.024s 0.708s 7 1.00x
🐘 Postgres Express 13.409s (-3.8%) 14.031s (-0.9%) 0.622s 7 1.01x
🐘 Postgres Next.js (Turbopack) 13.738s 14.020s 0.282s 7 1.03x
🐘 Postgres Nitro 14.007s (~) 14.306s (-1.0%) 0.299s 7 1.05x
💻 Local Next.js (Turbopack) 15.866s 16.195s 0.330s 6 1.19x
💻 Local Express 16.478s (-0.8%) 17.031s (~) 0.553s 6 1.24x
💻 Local Nitro 16.540s (-1.2%) 17.031s (~) 0.491s 6 1.24x
🌐 MongoDB Next.js (Turbopack) 20.378s 21.026s 0.648s 5 1.53x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 52.642s (+7.3% 🔺) 54.278s (+6.2% 🔺) 1.636s 2 1.00x
▲ Vercel Next.js (Turbopack) 53.856s (+7.3% 🔺) 55.079s (+5.9% 🔺) 1.223s 2 1.02x
▲ Vercel Nitro 55.821s (+17.0% 🔺) 57.263s (+16.0% 🔺) 1.443s 2 1.06x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.215s (-3.9%) 2.010s (~) 0.795s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.225s 2.009s 0.785s 15 1.01x
🐘 Postgres Nitro 1.258s (~) 2.009s (~) 0.751s 15 1.04x
🌐 Redis Next.js (Turbopack) 1.318s 2.006s 0.689s 15 1.08x
💻 Local Express 1.497s (-2.3%) 2.006s (~) 0.508s 15 1.23x
💻 Local Nitro 1.510s (-1.0%) 2.006s (~) 0.496s 15 1.24x
💻 Local Next.js (Turbopack) 1.571s 2.072s 0.501s 15 1.29x
🌐 MongoDB Next.js (Turbopack) 2.192s 3.008s 0.815s 10 1.80x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.361s (-10.4% 🟢) 3.788s (-15.6% 🟢) 1.427s 8 1.00x
▲ Vercel Nitro 2.418s (-24.3% 🟢) 3.960s (-17.7% 🟢) 1.541s 8 1.02x
▲ Vercel Express 2.649s (+12.8% 🔺) 4.366s (+4.4%) 1.716s 7 1.12x

🔍 Observability: Next.js (Turbopack) | Nitro | Express

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.308s (-1.1%) 3.011s (~) 0.703s 10 1.00x
🐘 Postgres Nitro 2.319s (-0.6%) 3.010s (~) 0.691s 10 1.00x
🐘 Postgres Next.js (Turbopack) 2.392s 3.010s 0.618s 10 1.04x
🌐 Redis Next.js (Turbopack) 2.565s 3.007s 0.442s 10 1.11x
💻 Local Express 2.797s (-7.4% 🟢) 3.007s (-18.2% 🟢) 0.210s 10 1.21x
💻 Local Next.js (Turbopack) 2.808s 3.208s 0.399s 10 1.22x
💻 Local Nitro 2.955s (+1.1%) 3.341s (+11.1% 🔺) 0.386s 9 1.28x
🌐 MongoDB Next.js (Turbopack) 8.558s 9.013s 0.455s 4 3.71x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.364s (-23.6% 🟢) 3.857s (-11.7% 🟢) 1.493s 8 1.00x
▲ Vercel Nitro 2.880s (-3.2%) 4.574s (-2.5%) 1.694s 8 1.22x
▲ Vercel Next.js (Turbopack) 3.144s (+17.9% 🔺) 4.948s (+14.0% 🔺) 1.803s 7 1.33x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 3.420s (-1.5%) 4.011s (~) 0.592s 8 1.00x
🐘 Postgres Nitro 3.575s (+3.7%) 4.133s (+3.0%) 0.559s 8 1.05x
🐘 Postgres Next.js (Turbopack) 3.650s 4.010s 0.360s 8 1.07x
🌐 Redis Next.js (Turbopack) 4.201s 4.868s 0.667s 7 1.23x
💻 Local Next.js (Turbopack) 7.133s 7.767s 0.635s 4 2.09x
💻 Local Express 7.778s (-3.3%) 8.271s (-2.9%) 0.493s 4 2.27x
💻 Local Nitro 8.754s (+5.6% 🔺) 9.271s (+2.8%) 0.517s 4 2.56x
🌐 MongoDB Next.js (Turbopack) 20.095s 20.527s 0.433s 2 5.88x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.606s (-21.7% 🟢) 4.030s (-18.4% 🟢) 1.425s 8 1.00x
▲ Vercel Express 2.745s (-7.7% 🟢) 4.524s (+2.7%) 1.779s 8 1.05x
▲ Vercel Next.js (Turbopack) 3.331s (+1.3%) 4.880s (+1.3%) 1.549s 7 1.28x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.193s (-4.7%) 2.008s (~) 0.815s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.219s 2.009s 0.790s 15 1.02x
🐘 Postgres Nitro 1.292s (+2.3%) 2.009s (~) 0.717s 15 1.08x
🌐 Redis Next.js (Turbopack) 1.340s 2.006s 0.666s 15 1.12x
💻 Local Next.js (Turbopack) 1.509s 2.006s 0.497s 15 1.26x
💻 Local Nitro 1.546s (-2.2%) 2.006s (-3.2%) 0.460s 15 1.30x
💻 Local Express 1.559s (+1.5%) 2.006s (~) 0.447s 15 1.31x
🌐 MongoDB Next.js (Turbopack) 3.193s 3.884s 0.691s 8 2.68x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.143s (+1.0%) 3.643s (-9.9% 🟢) 1.500s 9 1.00x
▲ Vercel Express 2.196s (-6.8% 🟢) 4.059s (-5.5% 🟢) 1.863s 8 1.02x
▲ Vercel Next.js (Turbopack) 2.211s (-22.3% 🟢) 3.523s (-24.7% 🟢) 1.313s 9 1.03x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.313s (-1.0%) 3.010s (~) 0.698s 10 1.00x
🐘 Postgres Nitro 2.336s (~) 3.010s (~) 0.674s 10 1.01x
🐘 Postgres Next.js (Turbopack) 2.378s 3.009s 0.631s 10 1.03x
🌐 Redis Next.js (Turbopack) 2.555s 3.008s 0.452s 10 1.10x
💻 Local Express 2.927s (-5.3% 🟢) 3.454s (-11.1% 🟢) 0.527s 9 1.27x
💻 Local Next.js (Turbopack) 2.961s 3.341s 0.380s 9 1.28x
💻 Local Nitro 2.961s (-3.2%) 3.564s (-8.2% 🟢) 0.603s 9 1.28x
🌐 MongoDB Next.js (Turbopack) 9.060s 9.762s 0.702s 4 3.92x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.393s (-12.7% 🟢) 4.108s (~) 1.715s 8 1.00x
▲ Vercel Nitro 2.401s (-24.6% 🟢) 4.034s (-17.5% 🟢) 1.633s 8 1.00x
▲ Vercel Next.js (Turbopack) 2.649s (+6.3% 🔺) 4.192s (+5.8% 🔺) 1.544s 8 1.11x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 3.425s (-1.7%) 4.011s (~) 0.586s 8 1.00x
🐘 Postgres Nitro 3.443s (-1.3%) 4.011s (~) 0.568s 8 1.01x
🐘 Postgres Next.js (Turbopack) 3.650s 4.014s 0.364s 8 1.07x
🌐 Redis Next.js (Turbopack) 4.239s 4.868s 0.629s 7 1.24x
💻 Local Next.js (Turbopack) 7.910s 8.519s 0.608s 4 2.31x
💻 Local Nitro 8.276s (-7.7% 🟢) 9.025s (-5.2% 🟢) 0.749s 4 2.42x
💻 Local Express 8.612s (~) 9.025s (~) 0.413s 4 2.51x
🌐 MongoDB Next.js (Turbopack) 19.657s 20.021s 0.363s 2 5.74x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.909s (-3.6%) 5.042s (+2.7%) 2.134s 6 1.00x
▲ Vercel Nitro 2.992s (-4.8%) 4.602s (-2.5%) 1.609s 7 1.03x
▲ Vercel Next.js (Turbopack) 3.208s (-9.0% 🟢) 4.771s (-12.8% 🟢) 1.563s 7 1.10x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.678s (-18.9% 🟢) 1.007s (~) 0.329s 60 1.00x
🌐 Redis Next.js (Turbopack) 0.727s 1.004s 0.277s 60 1.07x
🐘 Postgres Next.js (Turbopack) 0.753s 1.006s 0.253s 60 1.11x
🐘 Postgres Nitro 0.811s (-2.4%) 1.005s (~) 0.194s 60 1.20x
💻 Local Next.js (Turbopack) 0.828s 1.021s 0.193s 59 1.22x
💻 Local Express 0.973s (-0.9%) 1.038s (-8.6% 🟢) 0.065s 59 1.43x
💻 Local Nitro 0.992s (~) 1.114s (-9.4% 🟢) 0.122s 55 1.46x
🌐 MongoDB Next.js (Turbopack) 2.145s 3.008s 0.863s 20 3.16x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 9.307s (+10.9% 🔺) 11.290s (+15.6% 🔺) 1.982s 6 1.00x
▲ Vercel Nitro 9.603s (+9.7% 🔺) 11.141s (+5.0% 🔺) 1.538s 6 1.03x
▲ Vercel Next.js (Turbopack) 10.355s (+19.9% 🔺) 12.304s (+22.1% 🔺) 1.948s 5 1.11x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.615s (-19.2% 🟢) 2.008s (-20.9% 🟢) 0.393s 45 1.00x
🌐 Redis Next.js (Turbopack) 1.701s 2.006s 0.305s 45 1.05x
🐘 Postgres Next.js (Turbopack) 1.860s 2.053s 0.193s 44 1.15x
🐘 Postgres Nitro 1.934s (-1.4%) 2.174s (-3.8%) 0.240s 42 1.20x
💻 Local Next.js (Turbopack) 2.634s 3.008s 0.374s 30 1.63x
💻 Local Nitro 3.000s (-1.5%) 3.453s (-2.7%) 0.453s 27 1.86x
💻 Local Express 3.005s (-0.5%) 3.453s (-4.3%) 0.448s 27 1.86x
🌐 MongoDB Next.js (Turbopack) 5.210s 6.010s 0.801s 15 3.23x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 27.809s (+6.7% 🔺) 29.840s (+6.9% 🔺) 2.031s 4 1.00x
▲ Vercel Nitro 28.265s (+10.7% 🔺) 30.038s (+8.2% 🔺) 1.773s 4 1.02x
▲ Vercel Next.js (Turbopack) 30.742s (+11.8% 🔺) 32.286s (+10.1% 🔺) 1.544s 3 1.11x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 3.306s (-17.4% 🟢) 4.011s (-13.3% 🟢) 0.705s 30 1.00x
🌐 Redis Next.js (Turbopack) 3.308s 4.008s 0.700s 30 1.00x
🐘 Postgres Next.js (Turbopack) 3.716s 4.010s 0.294s 30 1.12x
🐘 Postgres Nitro 3.951s (~) 4.295s (~) 0.344s 28 1.20x
💻 Local Next.js (Turbopack) 8.586s 9.017s 0.431s 14 2.60x
💻 Local Nitro 9.163s (-1.1%) 9.787s (-2.3%) 0.624s 13 2.77x
💻 Local Express 9.211s (+0.7%) 9.787s (~) 0.577s 13 2.79x
🌐 MongoDB Next.js (Turbopack) 10.611s 11.017s 0.407s 11 3.21x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 75.194s (+3.8%) 76.855s (+3.0%) 1.661s 2 1.00x
▲ Vercel Express 75.826s (+3.9%) 78.174s (+4.4%) 2.348s 2 1.01x
▲ Vercel Next.js (Turbopack) 79.632s (+10.0% 🔺) 81.046s (+8.4% 🔺) 1.413s 2 1.06x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.246s 1.007s 0.761s 60 1.00x
🐘 Postgres Express 0.247s (-11.9% 🟢) 1.007s (~) 0.761s 60 1.00x
🐘 Postgres Nitro 0.277s (+0.7%) 1.007s (~) 0.730s 60 1.13x
🌐 Redis Next.js (Turbopack) 0.304s 1.004s 0.700s 60 1.24x
💻 Local Next.js (Turbopack) 0.527s 1.004s 0.477s 60 2.14x
💻 Local Nitro 0.593s (-1.4%) 1.004s (~) 0.412s 60 2.41x
💻 Local Express 0.616s (+8.1% 🔺) 1.004s (~) 0.389s 60 2.50x
🌐 MongoDB Next.js (Turbopack) 3.388s 3.882s 0.494s 16 13.78x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 1.336s (-26.0% 🟢) 2.826s (-16.4% 🟢) 1.489s 22 1.00x
▲ Vercel Nitro 1.523s (-18.3% 🟢) 3.068s (-11.7% 🟢) 1.545s 20 1.14x
▲ Vercel Next.js (Turbopack) 1.633s (-6.7% 🟢) 3.210s (-3.6%) 1.577s 19 1.22x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.389s (-21.3% 🟢) 1.006s (~) 0.617s 90 1.00x
🐘 Postgres Next.js (Turbopack) 0.466s 1.006s 0.540s 90 1.20x
🐘 Postgres Nitro 0.484s (-0.6%) 1.006s (~) 0.523s 90 1.24x
🌐 Redis Next.js (Turbopack) 1.164s 2.005s 0.841s 45 2.99x
💻 Local Nitro 2.528s (-0.8%) 3.009s (~) 0.481s 30 6.50x
💻 Local Next.js (Turbopack) 2.552s 3.009s 0.457s 30 6.56x
💻 Local Express 2.559s (~) 3.009s (~) 0.451s 30 6.58x
🌐 MongoDB Next.js (Turbopack) 9.373s 9.913s 0.540s 10 24.09x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 3.079s (-15.1% 🟢) 4.403s (-18.3% 🟢) 1.325s 21 1.00x
▲ Vercel Express 3.133s (+3.8%) 4.853s (+3.2%) 1.720s 19 1.02x
▲ Vercel Nitro 20.706s (+601.1% 🔺) 22.287s (+361.3% 🔺) 1.580s 17 6.73x

🔍 Observability: Next.js (Turbopack) | Express | Nitro

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.622s (-20.4% 🟢) 1.007s (~) 0.385s 120 1.00x
🐘 Postgres Next.js (Turbopack) 0.742s 1.006s 0.264s 120 1.19x
🐘 Postgres Nitro 0.790s (+3.5%) 1.008s (~) 0.218s 120 1.27x
🌐 Redis Next.js (Turbopack) 2.701s 3.057s 0.356s 40 4.34x
💻 Local Next.js (Turbopack) 10.404s 11.025s 0.621s 11 16.73x
💻 Local Nitro 11.018s (-2.8%) 11.754s (-2.3%) 0.736s 11 17.71x
💻 Local Express 11.313s (+1.4%) 11.937s (+1.5%) 0.624s 11 18.19x
🌐 MongoDB Next.js (Turbopack) 19.729s 20.188s 0.459s 6 31.72x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 7.143s (-3.0%) 8.539s (-7.8% 🟢) 1.396s 15 1.00x
▲ Vercel Express 7.408s (+10.5% 🔺) 9.070s (+8.9% 🔺) 1.662s 14 1.04x
▲ Vercel Nitro 7.969s (+8.8% 🔺) 9.489s (~) 1.521s 13 1.12x

🔍 Observability: Next.js (Turbopack) | Express | Nitro

Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 0.168s 1.003s 0.011s 1.017s 0.850s 10 1.00x
🌐 Redis Next.js (Turbopack) 0.179s 1.001s 0.001s 1.007s 0.828s 10 1.07x
🐘 Postgres Express 0.191s (-8.1% 🟢) 1.000s (~) 0.001s (-26.7% 🟢) 1.011s (~) 0.820s 10 1.14x
🐘 Postgres Nitro 0.194s (-18.0% 🟢) 0.996s (~) 0.001s (-81.0% 🟢) 1.010s (-0.6%) 0.816s 10 1.16x
🐘 Postgres Next.js (Turbopack) 0.196s 1.000s 0.001s 1.009s 0.814s 10 1.17x
💻 Local Nitro 0.200s (-1.1%) 1.004s (~) 0.012s (~) 1.018s (~) 0.818s 10 1.19x
💻 Local Express 0.204s (+1.1%) 1.005s (~) 0.011s (-0.9%) 1.017s (~) 0.814s 10 1.21x
🌐 MongoDB Next.js (Turbopack) 0.488s 0.967s 0.002s 1.008s 0.520s 10 2.91x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 1.466s (-13.8% 🟢) 2.692s (+2.2%) 0.682s (-2.5%) 3.769s (-5.8% 🟢) 2.303s 10 1.00x
▲ Vercel Nitro 1.592s (+4.2%) 2.757s (+9.7% 🔺) 0.529s (-20.9% 🟢) 3.700s (-3.0%) 2.108s 10 1.09x
▲ Vercel Express 1.654s (-12.3% 🟢) 3.451s (+20.1% 🔺) 0.122s (-68.1% 🟢) 4.050s (+2.3%) 2.397s 10 1.13x

🔍 Observability: Next.js (Turbopack) | Nitro | Express

stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 0.483s 1.001s 0.005s 1.012s 0.529s 60 1.00x
🐘 Postgres Express 0.554s (-8.2% 🟢) 1.008s (~) 0.004s (-12.1% 🟢) 1.022s (~) 0.468s 59 1.15x
🐘 Postgres Nitro 0.598s (~) 1.005s (~) 0.004s (-1.7%) 1.022s (~) 0.424s 59 1.24x
🐘 Postgres Next.js (Turbopack) 0.605s 1.009s 0.004s 1.022s 0.417s 59 1.25x
💻 Local Next.js (Turbopack) 0.654s 1.012s 0.010s 1.024s 0.370s 59 1.35x
💻 Local Nitro 0.818s (+12.8% 🔺) 1.012s (~) 0.009s (+6.3% 🔺) 1.116s (+9.0% 🔺) 0.298s 54 1.69x
💻 Local Express 0.824s (+14.4% 🔺) 1.012s (~) 0.009s (-2.0%) 1.113s (+8.8% 🔺) 0.289s 55 1.71x
🌐 MongoDB Next.js (Turbopack) 1.323s 1.958s 0.003s 2.012s 0.690s 30 2.74x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 4.107s (-7.0% 🟢) 5.588s (-4.2%) 0.394s (+125.8% 🔺) 6.348s (-5.6% 🟢) 2.240s 10 1.00x
▲ Vercel Express 4.335s (+3.4%) 5.771s (+7.1% 🔺) 0.433s (+154.4% 🔺) 6.624s (+5.4% 🔺) 2.289s 10 1.06x
▲ Vercel Next.js (Turbopack) 4.457s (-91.6% 🟢) 5.565s (-89.8% 🟢) 0.474s (+77.6% 🔺) 6.579s (-88.1% 🟢) 2.123s 10 1.09x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 0.907s 1.035s 0.000s 1.039s 0.132s 58 1.00x
🐘 Postgres Next.js (Turbopack) 0.918s 1.092s 0.000s 1.099s 0.180s 55 1.01x
🐘 Postgres Nitro 0.938s (-1.7%) 1.148s (+4.1%) 0.000s (-64.7% 🟢) 1.161s (+3.9%) 0.224s 52 1.03x
🐘 Postgres Express 0.962s (-0.6%) 1.159s (-8.6% 🟢) 0.000s (+Infinity% 🔺) 1.176s (-8.6% 🟢) 0.214s 52 1.06x
💻 Local Nitro 1.230s (~) 2.020s (~) 0.000s (-25.0% 🟢) 2.022s (~) 0.792s 30 1.36x
💻 Local Express 1.245s (-0.9%) 2.021s (~) 0.000s (+55.6% 🔺) 2.023s (~) 0.778s 30 1.37x
💻 Local Next.js (Turbopack) 1.288s 2.021s 0.000s 2.024s 0.736s 30 1.42x
🌐 MongoDB Next.js (Turbopack) 2.351s 2.956s 0.000s 3.007s 0.656s 20 2.59x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.504s (-10.2% 🟢) 3.470s (-1.5%) 0.001s (+275.0% 🔺) 3.838s (-9.4% 🟢) 1.334s 16 1.00x
▲ Vercel Express 3.054s (+9.4% 🔺) 4.326s (+15.8% 🔺) 0.003s (+Infinity% 🔺) 4.813s (+11.8% 🔺) 1.759s 13 1.22x
▲ Vercel Next.js (Turbopack) 3.443s (+17.6% 🔺) 4.655s (+20.4% 🔺) 0.000s (-41.7% 🟢) 5.026s (+9.4% 🔺) 1.583s 12 1.38x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

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

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 1.607s 2.035s 0.000s 2.039s 0.432s 30 1.00x
🐘 Postgres Nitro 1.705s (-10.2% 🟢) 2.101s (-10.3% 🟢) 0.000s (+34.5% 🔺) 2.118s (-10.1% 🟢) 0.413s 29 1.06x
🐘 Postgres Next.js (Turbopack) 1.796s 2.072s 0.000s 2.128s 0.332s 29 1.12x
🐘 Postgres Express 1.855s (+7.1% 🔺) 2.298s (+9.4% 🔺) 0.000s (-100.0% 🟢) 2.306s (+9.1% 🔺) 0.451s 27 1.15x
💻 Local Nitro 3.508s (+1.5%) 4.031s (~) 0.001s (~) 4.034s (~) 0.526s 15 2.18x
💻 Local Express 3.550s (-1.6%) 3.970s (-3.2%) 0.000s (-71.9% 🟢) 3.972s (-3.2%) 0.422s 16 2.21x
💻 Local Next.js (Turbopack) 3.751s 4.233s 0.000s 4.237s 0.486s 15 2.33x
🌐 MongoDB Next.js (Turbopack) 4.382s 4.949s 0.000s 5.010s 0.628s 12 2.73x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.541s (-20.7% 🟢) 4.631s (-17.1% 🟢) 0.000s (-82.2% 🟢) 4.992s (-19.6% 🟢) 1.451s 13 1.00x
▲ Vercel Express 3.820s (-22.8% 🟢) 5.232s (-17.4% 🟢) 0.000s (-90.9% 🟢) 5.674s (-18.0% 🟢) 1.854s 11 1.08x
▲ Vercel Next.js (Turbopack) 4.243s (-15.2% 🟢) 5.258s (-12.2% 🟢) 0.000s (+145.5% 🔺) 5.682s (-15.9% 🟢) 1.439s 11 1.20x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Next.js (Turbopack) 14/21
🐘 Postgres Express 18/21
▲ Vercel Nitro 8/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 17/21
Next.js (Turbopack) 🌐 Redis 8/21
Nitro 🐘 Postgres 17/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)

📋 View full workflow run

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 27, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 879 0 67 946
✅ 💻 Local Development 854 0 178 1032
✅ 📦 Local Production 854 0 178 1032
✅ 🐘 Local Postgres 854 0 178 1032
✅ 🪟 Windows 78 0 8 86
❌ 🌍 Community Worlds 135 63 24 222
✅ 📋 Other 216 0 42 258
Total 3870 63 675 4608

❌ Failed Tests

🌍 Community Worlds (63 failed)

mongodb (3 failed):

  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KNJ2H4TCETAD1HR9ZM7P850C
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KNJ2S1J4WJ4YTVWKYXBH492V
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_01KNJ2ZSB7T9QX5Q7K2SFJYXZX

redis (3 failed):

  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KNJ2H4TCETAD1HR9ZM7P850C
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KNJ2S1J4WJ4YTVWKYXBH492V
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_01KNJ2ZSB7T9QX5Q7K2SFJYXZX

turso (57 failed):

  • addTenWorkflow | wrun_01KNJ2FYQBZTY9S3GT49SGD4YQ
  • addTenWorkflow | wrun_01KNJ2FYQBZTY9S3GT49SGD4YQ
  • wellKnownAgentWorkflow (.well-known/agent) | wrun_01KNJ2H6PYH908JYFRABH222F5
  • should work with react rendering in step
  • promiseAllWorkflow | wrun_01KNJ2G604RZZRP7KEHHDKKZRJ
  • promiseRaceWorkflow | wrun_01KNJ2GACW0DR46AC8G8QGHV0W
  • promiseAnyWorkflow | wrun_01KNJ2GCPTYCN0YPZ4HRXZ9HTK
  • importedStepOnlyWorkflow | wrun_01KNJ2HK8ESKH5DDEPJ19KVTED
  • hookWorkflow | wrun_01KNJ2GRBCNRAWHQJZCHJQ7HNH
  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KNJ2H4TCETAD1HR9ZM7P850C
  • webhookWorkflow | wrun_01KNJ2HEMMFWH1989CDTJTZY3Q
  • sleepingWorkflow | wrun_01KNJ2HNJNX4DHQGF6KZ6BP9QF
  • parallelSleepWorkflow | wrun_01KNJ2J1Q6QRA5W14E81KYQHHP
  • nullByteWorkflow | wrun_01KNJ2J669D3BWD8REYZK8DM4Q
  • workflowAndStepMetadataWorkflow | wrun_01KNJ2J87J7Z9YNTDN5CHNZZ86
  • fetchWorkflow | wrun_01KNJ2MW79CJR29TJSDXTD74T0
  • promiseRaceStressTestWorkflow | wrun_01KNJ2MZNZYEFB7KGP2Q50ZXHF
  • error handling error propagation workflow errors nested function calls preserve message and stack trace
  • error handling error propagation workflow errors cross-file imports preserve message and stack trace
  • error handling error propagation step errors basic step error preserves message and stack trace
  • error handling error propagation step errors cross-file step error preserves message and function names in stack
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • error handling not registered WorkflowNotRegisteredError fails the run when workflow does not exist
  • error handling not registered StepNotRegisteredError fails the step but workflow can catch it
  • error handling not registered StepNotRegisteredError fails the run when not caught in workflow
  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_01KNJ2RDE42XNNG6S6T9A37TQ2
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KNJ2S1J4WJ4YTVWKYXBH492V
  • hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running | wrun_01KNJ2SRA9E237K44GZGYHBPDG
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars) | wrun_01KNJ2TBNWF2BG2H4CWKQP5E3S
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument | wrun_01KNJ2TMS5MEHZKPQBZXFASYFX
  • closureVariableWorkflow - nested step functions with closure variables | wrun_01KNJ2TT2HJKAMXV0DGV07K3VA
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step | wrun_01KNJ2TW5PES5HDD00Y8RE62CQ
  • health check (queue-based) - workflow and step endpoints respond to health check messages
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly | wrun_01KNJ2VBV6WCB75R4D1TWQTSSH
  • Calculator.calculate - static workflow method using static step methods from another class | wrun_01KNJ2VH5R69VF04AT4P7CXPG5
  • AllInOneService.processNumber - static workflow method using sibling static step methods | wrun_01KNJ2VR1JPEYSHES7P2F3CAM8
  • ChainableService.processWithThis - static step methods using this to reference the class | wrun_01KNJ2VYDCXDQ0PY510ED46Q0D
  • thisSerializationWorkflow - step function invoked with .call() and .apply() | wrun_01KNJ2W91H1NCVGMEY6C79DXBQ
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE | wrun_01KNJ2WPKKS7PVZZ5Y4NS8VXZJ
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_01KNJ2WXVY3AW7N6XA2T8HC4C0
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context | wrun_01KNJ2X9AQ76135RVQ14T0SBY6
  • stepFunctionAsStartArgWorkflow - step function reference passed as start() argument | wrun_01KNJ2XH0PCZQ6N9T988MNS4S5
  • cancelRun - cancelling a running workflow | wrun_01KNJ2XQCB86HVVXEJKMHGN77J
  • cancelRun via CLI - cancelling a running workflow | wrun_01KNJ2Y0Q18XSZFG23J0HTAE32
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router
  • hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep | wrun_01KNJ2YC6RG3JZ7FMCV7GS76T4
  • sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration | wrun_01KNJ2Z10S1AM95QY01TT35NYM
  • sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) | wrun_01KNJ2ZDSM3P8YZ31CZB5K1YCQ
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_01KNJ2ZM342RAJJRJ4GM1S2JX0
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_01KNJ2ZQ7KE54B5E7B116PB9R0
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_01KNJ2ZSB7T9QX5Q7K2SFJYXZX

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 79 0 7
✅ example 79 0 7
✅ express 79 0 7
✅ fastify 79 0 7
✅ hono 79 0 7
✅ nextjs-turbopack 84 0 2
✅ nextjs-webpack 84 0 2
✅ nitro 79 0 7
✅ nuxt 79 0 7
✅ sveltekit 79 0 7
✅ vite 79 0 7
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 72 0 14
✅ express-stable 72 0 14
✅ fastify-stable 72 0 14
✅ hono-stable 72 0 14
✅ nextjs-turbopack-canary 61 0 25
✅ nextjs-turbopack-stable 78 0 8
✅ nextjs-webpack-canary 61 0 25
✅ nextjs-webpack-stable 78 0 8
✅ nitro-stable 72 0 14
✅ nuxt-stable 72 0 14
✅ sveltekit-stable 72 0 14
✅ vite-stable 72 0 14
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 72 0 14
✅ express-stable 72 0 14
✅ fastify-stable 72 0 14
✅ hono-stable 72 0 14
✅ nextjs-turbopack-canary 61 0 25
✅ nextjs-turbopack-stable 78 0 8
✅ nextjs-webpack-canary 61 0 25
✅ nextjs-webpack-stable 78 0 8
✅ nitro-stable 72 0 14
✅ nuxt-stable 72 0 14
✅ sveltekit-stable 72 0 14
✅ vite-stable 72 0 14
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 72 0 14
✅ express-stable 72 0 14
✅ fastify-stable 72 0 14
✅ hono-stable 72 0 14
✅ nextjs-turbopack-canary 61 0 25
✅ nextjs-turbopack-stable 78 0 8
✅ nextjs-webpack-canary 61 0 25
✅ nextjs-webpack-stable 78 0 8
✅ nitro-stable 72 0 14
✅ nuxt-stable 72 0 14
✅ sveltekit-stable 72 0 14
✅ vite-stable 72 0 14
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 78 0 8
❌ 🌍 Community Worlds
App Passed Failed Skipped
✅ mongodb-dev 5 0 0
❌ mongodb 58 3 8
✅ redis-dev 5 0 0
❌ redis 58 3 8
✅ turso-dev 5 0 0
❌ turso 4 57 8
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 72 0 14
✅ e2e-local-postgres-nest-stable 72 0 14
✅ e2e-local-prod-nest-stable 72 0 14

📋 View full workflow run

Comment thread packages/core/src/runtime/start.ts Outdated
const encodedInput =
workflowArguments instanceof Uint8Array
? Array.from(workflowArguments)
: workflowArguments;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concrete suggestion for the queue transport issue: @vercel/queue offers a BufferTransport out of the box. The fix here is:

  1. CBOR-encode the WorkflowInvokePayload before dispatch
  2. Use BufferTransport instead of the default JsonTransport
  3. CBOR-decode on the receive side

This eliminates the entire encode/decode problem — Uint8Array values survive CBOR natively. No base64, no Array.from, no discriminant fields. The input field just carries the raw Uint8Array through transparently.

This would also be a net improvement for all queue messages (not just resilient start), since CBOR is more compact than JSON for binary-heavy payloads.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented. We went with a custom CborTransport (rather than BufferTransport) so the transport handles both encode and decode internally — call sites pass plain objects, the handler receives decoded objects. See world-vercel/queue.ts for the implementation. world-local and world-postgres use TypedJsonTransport with tagged envelopes since they don't have VQS's BufferTransport available.

Signed-off-by: Peter Wielander <mittgfu@gmail.com>
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
Comment thread packages/core/src/runtime.ts Outdated
vercel bot and others added 8 commits April 1, 2026 21:42
…ts.js' causes TS2300 build failure.

This commit fixes the issue reported at packages/core/src/runtime.ts:18

**Bug:** Lines 18-22 of `packages/core/src/runtime.ts` contain two separate import statements from `'./runtime/constants.js'`:
1. Line 18: `import { MAX_QUEUE_DELIVERIES } from './runtime/constants.js';`
2. Lines 19-22: `import { MAX_QUEUE_DELIVERIES, REPLAY_TIMEOUT_MS } from './runtime/constants.js';`

Both import `MAX_QUEUE_DELIVERIES`, creating a duplicate identifier. This is a merge conflict artifact - the first import was from the PR's reordering of imports, and the second was added by a merge from main that introduced `REPLAY_TIMEOUT_MS`. The TypeScript compiler rejects this with error TS2300 on both lines 18 and 20, causing the `@workflow/core` package build to fail, which in turn fails the entire Vercel deployment.

The build log confirms:
```
src/runtime.ts(18,10): error TS2300: Duplicate identifier 'MAX_QUEUE_DELIVERIES'.
src/runtime.ts(20,3): error TS2300: Duplicate identifier 'MAX_QUEUE_DELIVERIES'.
```

**Fix:** Consolidated the two import statements into a single import:
```typescript
import { MAX_QUEUE_DELIVERIES, REPLAY_TIMEOUT_MS } from './runtime/constants.js';
```

Both symbols are used in the file - `MAX_QUEUE_DELIVERIES` for queue delivery limits and `REPLAY_TIMEOUT_MS` on lines 174, 185, and 198 for replay timeout configuration.

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: VaguelySerious <mittgfu@gmail.com>
Replace stock JsonTransport with a custom transport that encodes
Uint8Array values as { __type: 'Uint8Array', data: '<base64>' }
during JSON serialization. Without this, runInput.input (a Uint8Array)
gets corrupted to a plain object when sent through the postgres queue,
causing 'Invalid input' errors in the resilient start e2e test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
VaguelySerious and others added 8 commits April 3, 2026 12:56
Signed-off-by: Peter Wielander <mittgfu@gmail.com>

# Conflicts:
#	packages/core/src/runtime/start.test.ts
#	packages/world-local/src/storage.test.ts
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
# Conflicts:
#	packages/core/e2e/e2e.test.ts
…t duplicate events

The normal run_created path used writeJSON (fs.access + temp+rename) which
has a TOCTOU race with the resilient start path's writeExclusive. On the
local world, both events.create(run_created) and events.create(run_started)
run concurrently in the same event loop. Both could pass the existence check
simultaneously, resulting in two run_created events — causing "Unconsumed
event in event log" errors during replay.

Switch the normal run_created entity write to writeExclusive (O_CREAT|O_EXCL)
so exactly one writer wins atomically. Fixes consistent Windows CI failures
in world-testing embedded tests.

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

The Vite-builder workbenches (astro, sveltekit) have committed step.js
bundles that register builtins with bare names (e.g. "__builtin_response_text").
The workflow VM looks them up with full IDs from builtinStepId(). The previous
suffix match (endsWith("//{name}")) missed bare-name registrations since they
don't contain "//".

Fix by also checking for exact bare-name matches, and extracting the function
name from fully-qualified step IDs before matching. Also expand the builtin
allowlist to cover start() and Run.* methods.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CborTransport was a pass-through wrapper — serialize() was an identity
function and deserialize() returned raw Buffers. The actual CBOR
encode/decode happened at call sites (queue() pre-encoded, handler
post-decoded). This violated the transport abstraction and required
callers to remember to handle encoding.

Move encode()/decode() into CborTransport.serialize()/deserialize()
so the transport is self-contained, matching TypedJsonTransport
(world-local) and the inline transport (world-postgres). Call sites
now pass plain objects; the handler receives decoded objects.

Also update follow-up items in resilient-start.mdx:
- Mark Local Prod flakiness as resolved
- Close events optimization for re-enqueue (won't-do: unsafe with
  at-least-once delivery)
- Mark CborTransport refactor as done

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

@TooTallNate TooTallNate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewing the latest state: most of the previous blockers are resolved (changeset is patch, binary-safe queue transports are in place, eventData ||= eventDataJson fallback exists, and EntityConflictError handling is restored).

I found one remaining correctness issue that should be fixed before merge:

  • Blocking: resilient start can lose the caller's requested specVersion.

start() includes runInput.specVersion in the queue payload, but workflowEntrypoint still always emits run_started with specVersion: SPEC_VERSION_CURRENT. If run_created fails and the run is created through the resilient path, world-local/world-postgres use the event's specVersion (current), not the original one requested by start().

This breaks the contract for callers explicitly using legacy spec versions.

Suggested direction:

  1. In workflowEntrypoint, when runInput is present, set run_started.specVersion from runInput.specVersion (validated), otherwise default to current.
  2. Keep runInput schema validation strict (specVersion required) and rely on existing world storage logic that uses effectiveSpecVersion from the event.

Comment thread packages/core/src/runtime.ts Outdated
@@ -258,6 +250,20 @@ export function workflowEntrypoint(
{
eventType: 'run_started',
specVersion: SPEC_VERSION_CURRENT,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: This always uses SPEC_VERSION_CURRENT, even when runInput is present and includes its own specVersion.

In resilient start, run_created may fail and the run gets created from run_started in the world layer. Both world-local and world-postgres resilient creation paths use effectiveSpecVersion from the event (data.specVersion), so this line effectively forces the run to current spec version.

That means a caller doing start(..., { specVersion: 1 }) can be silently upgraded to current spec when resilient start kicks in.

Please use runInput.specVersion when runInput is present, and only default to SPEC_VERSION_CURRENT when it's absent.

workflowEntrypoint hardcoded specVersion: SPEC_VERSION_CURRENT on
run_started events. When the resilient start path creates the run
from run_started (because run_created failed), the run was always
created with the current spec version, ignoring the version originally
requested by start(). This breaks callers using legacy spec versions.

Use runInput.specVersion (carried through the queue from start()) when
available, falling back to SPEC_VERSION_CURRENT for re-enqueue cycles
where runInput is absent.

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

@TooTallNate TooTallNate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed after 90b9a7f5 — the remaining blocker is resolved.

workflowEntrypoint now uses runInput?.specVersion ?? SPEC_VERSION_CURRENT when creating run_started, so resilient-start run creation preserves the caller-requested spec version instead of always forcing current. That fixes the legacy specVersion drift issue I flagged.

Given that, LGTM.

Minor follow-up (non-blocking): adding a regression test for the resilient path with start(..., { specVersion: 1 }) would be great, so this behavior stays locked in.

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.

2 participants