Skip to content

Fix concurrent wait_completed race condition in world-local#1388

Open
TooTallNate wants to merge 2 commits intomainfrom
fix/world-local-duplicate-wait-completed
Open

Fix concurrent wait_completed race condition in world-local#1388
TooTallNate wants to merge 2 commits intomainfrom
fix/world-local-duplicate-wait-completed

Conversation

@TooTallNate
Copy link
Member

Summary

  • Fix a TOCTOU race condition in world-local where two concurrent runtime invocations could both create wait_completed events for the same wait, causing duplicate events in the event log
  • The duplicate wait_completed caused WorkflowRuntimeError: Unconsumed event during replay, since the sleep callback consumed the first event and was removed, leaving the second with no consumer
  • Use writeExclusive (O_CREAT|O_EXCL) to atomically claim a .completed lock file before transitioning a wait to completed — if a concurrent invocation already claimed it, the second gets a 409 which the runtime's existing conflict handler gracefully skips
  • Add createWait and completeWait test helpers
  • Add tests for wait lifecycle including a concurrent race regression test (Promise.allSettled with two simultaneous completions — exactly one succeeds, the other gets 409)

Use writeExclusive (O_CREAT|O_EXCL) to atomically prevent concurrent
invocations from both completing the same wait. Previously, two
concurrent runtime invocations could both read the wait as 'waiting'
and both create wait_completed events, causing duplicate events in the
event log. On replay, the sleep callback consumed the first and the
second was reported as an unconsumed event.

Add wait test helpers (createWait, completeWait) and tests for:
- Basic wait creation and completion
- Duplicate wait_created rejection
- Sequential duplicate wait_completed rejection (409)
- Concurrent wait_completed race (Promise.allSettled, exactly one wins)
@vercel
Copy link
Contributor

vercel bot commented Mar 14, 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 Mar 14, 2026 9:38am
example-nextjs-workflow-webpack Ready Ready Preview, Comment Mar 14, 2026 9:38am
example-workflow Ready Ready Preview, Comment Mar 14, 2026 9:38am
workbench-astro-workflow Ready Ready Preview, Comment Mar 14, 2026 9:38am
workbench-express-workflow Ready Ready Preview, Comment Mar 14, 2026 9:38am
workbench-fastify-workflow Ready Ready Preview, Comment Mar 14, 2026 9:38am
workbench-hono-workflow Ready Ready Preview, Comment Mar 14, 2026 9:38am
workbench-nitro-workflow Ready Ready Preview, Comment Mar 14, 2026 9:38am
workbench-nuxt-workflow Ready Ready Preview, Comment Mar 14, 2026 9:38am
workbench-sveltekit-workflow Ready Ready Preview, Comment Mar 14, 2026 9:38am
workbench-vite-workflow Ready Ready Preview, Comment Mar 14, 2026 9:38am
workflow-docs Ready Ready Preview, Comment, Open in v0 Mar 14, 2026 9:38am
workflow-nest Ready Ready Preview, Comment Mar 14, 2026 9:38am
workflow-swc-playground Ready Ready Preview, Comment Mar 14, 2026 9:38am

@TooTallNate TooTallNate requested a review from a team as a code owner March 14, 2026 09:32
Copilot AI review requested due to automatic review settings March 14, 2026 09:32
@changeset-bot
Copy link

changeset-bot bot commented Mar 14, 2026

🦋 Changeset detected

Latest commit: 8aa0385

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

This PR includes changesets to release 18 packages
Name Type
@workflow/world-local Patch
@workflow/core Patch
workflow Patch
@workflow/cli Patch
@workflow/vitest Patch
@workflow/world-postgres Patch
@workflow/builders Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/web-shared Patch
@workflow/world-testing Patch
@workflow/ai Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/nuxt 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

@github-actions
Copy link
Contributor

github-actions bot commented Mar 14, 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.042s (+29.6% 🔺) 1.005s (~) 0.963s 10 1.00x
💻 Local Express 0.043s (+32.7% 🔺) 1.006s (~) 0.963s 10 1.01x
💻 Local Next.js (Turbopack) 0.049s (+15.9% 🔺) 1.005s (~) 0.957s 10 1.16x
🐘 Postgres Express 0.050s (-6.9% 🟢) 1.011s (~) 0.961s 10 1.19x
🌐 Redis Next.js (Turbopack) 0.056s (+34.5% 🔺) 1.005s (~) 0.950s 10 1.33x
🐘 Postgres Next.js (Turbopack) 0.061s (+10.0% 🔺) 1.012s (~) 0.951s 10 1.45x
🐘 Postgres Nitro 0.061s (+68.1% 🔺) 1.012s (~) 0.951s 10 1.46x
🌐 MongoDB Next.js (Turbopack) 0.109s (+31.1% 🔺) 1.008s (~) 0.899s 10 2.60x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 0.427s (-12.3% 🟢) 1.945s (-7.1% 🟢) 1.518s 10 1.00x
▲ Vercel Nitro 0.437s (+12.8% 🔺) 2.344s (+15.9% 🔺) 1.908s 10 1.02x
▲ Vercel Express 0.459s (-5.7% 🟢) 2.145s (-19.0% 🟢) 1.686s 10 1.07x

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

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.113s (-1.1%) 2.012s (~) 0.898s 10 1.00x
💻 Local Next.js (Turbopack) 1.118s (+1.3%) 2.006s (~) 0.889s 10 1.00x
💻 Local Express 1.120s (+1.6%) 2.006s (~) 0.885s 10 1.01x
🌐 Redis Next.js (Turbopack) 1.121s (+3.1%) 2.007s (~) 0.886s 10 1.01x
💻 Local Nitro 1.124s (+1.8%) 2.005s (~) 0.881s 10 1.01x
🐘 Postgres Nitro 1.143s (+4.6%) 2.014s (~) 0.870s 10 1.03x
🐘 Postgres Next.js (Turbopack) 1.159s (+2.6%) 2.013s (~) 0.854s 10 1.04x
🌐 MongoDB Next.js (Turbopack) 1.296s (+0.6%) 2.009s (~) 0.713s 10 1.16x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 1.942s (-8.3% 🟢) 3.325s (-10.1% 🟢) 1.383s 10 1.00x
▲ Vercel Nitro 2.004s (-0.6%) 3.567s (+3.2%) 1.563s 10 1.03x
▲ Vercel Next.js (Turbopack) 2.019s (-0.9%) 3.122s (-7.5% 🟢) 1.103s 10 1.04x

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

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 10.757s (+1.8%) 11.022s (~) 0.265s 3 1.00x
🐘 Postgres Express 10.764s (~) 11.039s (~) 0.274s 3 1.00x
💻 Local Next.js (Turbopack) 10.813s (+1.3%) 11.024s (~) 0.211s 3 1.01x
🐘 Postgres Next.js (Turbopack) 10.883s (+0.7%) 11.043s (~) 0.160s 3 1.01x
💻 Local Express 10.883s (+0.9%) 11.022s (~) 0.139s 3 1.01x
💻 Local Nitro 10.883s (+1.0%) 11.022s (~) 0.138s 3 1.01x
🐘 Postgres Nitro 11.011s (+4.2%) 11.383s (+3.1%) 0.371s 3 1.02x
🌐 MongoDB Next.js (Turbopack) 12.100s (~) 12.690s (~) 0.590s 3 1.12x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 16.111s (-4.2%) 17.991s (~) 1.879s 2 1.00x
▲ Vercel Express 16.473s (-2.6%) 17.935s (-1.6%) 1.463s 2 1.02x
▲ Vercel Next.js (Turbopack) 16.773s (-2.4%) 17.702s (-6.7% 🟢) 0.929s 2 1.04x

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

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 26.623s (-1.3%) 27.057s (~) 0.434s 3 1.00x
🌐 Redis Next.js (Turbopack) 26.759s (+1.8%) 27.051s (~) 0.292s 3 1.01x
🐘 Postgres Next.js (Turbopack) 27.077s (+1.0%) 27.395s (+1.2%) 0.318s 3 1.02x
💻 Local Next.js (Turbopack) 27.118s (~) 28.052s (+3.7%) 0.934s 3 1.02x
🐘 Postgres Nitro 27.166s (+3.1%) 28.066s (+3.7%) 0.900s 3 1.02x
💻 Local Express 27.426s (+0.8%) 28.051s (~) 0.624s 3 1.03x
💻 Local Nitro 27.438s (+0.9%) 28.051s (~) 0.613s 3 1.03x
🌐 MongoDB Next.js (Turbopack) 30.290s (+0.5%) 31.045s (+1.6%) 0.755s 2 1.14x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 42.092s (-4.9%) 43.655s (-5.4% 🟢) 1.563s 2 1.00x
▲ Vercel Nitro 42.809s (-3.9%) 44.505s (-2.9%) 1.696s 2 1.02x
▲ Vercel Next.js (Turbopack) 43.145s (-3.6%) 44.502s (-2.7%) 1.357s 2 1.03x

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

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 53.353s (-0.9%) 54.092s (~) 0.739s 2 1.00x
🌐 Redis Next.js (Turbopack) 53.370s (+1.6%) 54.100s (+1.9%) 0.730s 2 1.00x
🐘 Postgres Next.js (Turbopack) 54.109s (+0.6%) 55.111s (+1.9%) 1.002s 2 1.01x
🐘 Postgres Nitro 54.373s (+3.2%) 55.108s (+3.8%) 0.736s 2 1.02x
💻 Local Next.js (Turbopack) 55.938s (+0.6%) 56.104s (~) 0.166s 2 1.05x
💻 Local Express 56.399s (+0.6%) 57.100s (+1.8%) 0.701s 2 1.06x
💻 Local Nitro 56.523s (+0.9%) 57.100s (+1.8%) 0.576s 2 1.06x
🌐 MongoDB Next.js (Turbopack) 60.453s (-0.6%) 61.078s (~) 0.624s 2 1.13x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 90.611s (-12.5% 🟢) 91.807s (-12.0% 🟢) 1.196s 1 1.00x
▲ Vercel Express 91.224s (-5.3% 🟢) 93.054s (-5.4% 🟢) 1.830s 1 1.01x
▲ Vercel Next.js (Turbopack) 92.069s (-6.8% 🟢) 92.888s (-7.2% 🟢) 0.819s 1 1.02x

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

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 1.310s (+9.5% 🔺) 2.006s (~) 0.697s 15 1.00x
🐘 Postgres Express 1.341s (~) 2.011s (~) 0.671s 15 1.02x
🐘 Postgres Nitro 1.428s (+11.5% 🔺) 2.011s (~) 0.583s 15 1.09x
🐘 Postgres Next.js (Turbopack) 1.490s (+7.7% 🔺) 2.012s (~) 0.522s 15 1.14x
💻 Local Nitro 1.498s (+5.8% 🔺) 2.006s (~) 0.508s 15 1.14x
💻 Local Express 1.511s (+6.8% 🔺) 2.005s (~) 0.494s 15 1.15x
💻 Local Next.js (Turbopack) 1.535s (+7.5% 🔺) 2.005s (~) 0.470s 15 1.17x
🌐 MongoDB Next.js (Turbopack) 2.139s (~) 3.009s (~) 0.870s 10 1.63x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.110s (-15.1% 🟢) 3.105s (-17.8% 🟢) 0.995s 10 1.00x
▲ Vercel Nitro 2.255s (-8.6% 🟢) 3.699s (+6.0% 🔺) 1.444s 9 1.07x
▲ Vercel Express 2.524s (+2.7%) 3.650s (-7.3% 🟢) 1.125s 9 1.20x

🔍 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.498s (+2.3%) 3.013s (~) 0.515s 10 1.00x
🌐 Redis Next.js (Turbopack) 2.580s (+7.4% 🔺) 3.008s (~) 0.428s 10 1.03x
🐘 Postgres Next.js (Turbopack) 2.618s (+1.9%) 3.013s (~) 0.396s 10 1.05x
💻 Local Express 2.805s (+7.2% 🔺) 3.007s (~) 0.202s 10 1.12x
💻 Local Next.js (Turbopack) 2.893s (+11.5% 🔺) 3.455s (+14.9% 🔺) 0.562s 9 1.16x
💻 Local Nitro 2.982s (+13.8% 🔺) 3.208s (+6.7% 🔺) 0.226s 10 1.19x
🌐 MongoDB Next.js (Turbopack) 4.737s (+1.7%) 5.179s (~) 0.442s 6 1.90x
🐘 Postgres Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.468s (-6.9% 🟢) 3.277s (-10.1% 🟢) 0.809s 10 1.00x
▲ Vercel Express 2.562s (-0.9%) 3.678s (-9.1% 🟢) 1.116s 9 1.04x
▲ Vercel Nitro 2.926s (~) 4.187s (+2.9%) 1.260s 8 1.19x

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

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 3.962s (+0.7%) 4.589s (+3.2%) 0.627s 7 1.00x
🐘 Postgres Nitro 4.066s (+10.8% 🔺) 4.446s (+10.7% 🔺) 0.380s 7 1.03x
🌐 Redis Next.js (Turbopack) 4.197s (+8.0% 🔺) 4.868s (+17.7% 🔺) 0.671s 7 1.06x
🐘 Postgres Next.js (Turbopack) 4.339s (+5.5% 🔺) 5.017s (~) 0.678s 6 1.10x
💻 Local Next.js (Turbopack) 7.259s (+5.4% 🔺) 7.767s (+3.3%) 0.508s 4 1.83x
💻 Local Express 7.633s (+0.9%) 8.020s (~) 0.387s 4 1.93x
💻 Local Nitro 7.919s (+7.4% 🔺) 8.268s (+3.1%) 0.349s 4 2.00x
🌐 MongoDB Next.js (Turbopack) 9.840s (~) 10.350s (~) 0.510s 3 2.48x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.708s (-3.0%) 3.669s (-16.3% 🟢) 0.960s 9 1.00x
▲ Vercel Express 2.744s (-14.2% 🟢) 3.731s (-15.8% 🟢) 0.986s 9 1.01x
▲ Vercel Nitro 2.818s (-12.6% 🟢) 4.180s (-3.9%) 1.361s 8 1.04x

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

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 1.254s (+4.5%) 2.006s (~) 0.752s 15 1.00x
🐘 Postgres Express 1.347s (+1.7%) 2.011s (~) 0.664s 15 1.07x
🐘 Postgres Nitro 1.418s (+13.1% 🔺) 2.011s (~) 0.593s 15 1.13x
🐘 Postgres Next.js (Turbopack) 1.454s (+5.1% 🔺) 2.012s (~) 0.557s 15 1.16x
💻 Local Nitro 1.513s (+5.6% 🔺) 2.005s (~) 0.493s 15 1.21x
💻 Local Next.js (Turbopack) 1.514s (+5.6% 🔺) 2.005s (~) 0.491s 15 1.21x
💻 Local Express 1.538s (+6.0% 🔺) 2.005s (~) 0.467s 15 1.23x
🌐 MongoDB Next.js (Turbopack) 2.159s (~) 3.009s (~) 0.851s 10 1.72x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.019s (-1.2%) 3.577s (+13.9% 🔺) 1.558s 9 1.00x
▲ Vercel Express 2.036s (-6.9% 🟢) 3.277s (-9.8% 🟢) 1.241s 10 1.01x
▲ Vercel Next.js (Turbopack) 2.092s (-5.6% 🟢) 3.105s (-7.4% 🟢) 1.013s 10 1.04x

🔍 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.472s (+0.9%) 3.011s (~) 0.539s 10 1.00x
🐘 Postgres Nitro 2.504s (+4.3%) 3.013s (~) 0.509s 10 1.01x
🌐 Redis Next.js (Turbopack) 2.599s (+8.2% 🔺) 3.008s (~) 0.409s 10 1.05x
🐘 Postgres Next.js (Turbopack) 2.671s (-0.9%) 3.014s (-3.2%) 0.343s 10 1.08x
💻 Local Express 2.895s (+8.5% 🔺) 3.342s (+11.1% 🔺) 0.447s 9 1.17x
💻 Local Nitro 2.916s (+6.6% 🔺) 3.008s (~) 0.092s 10 1.18x
💻 Local Next.js (Turbopack) 2.994s (+6.9% 🔺) 3.209s (+3.2%) 0.215s 10 1.21x
🌐 MongoDB Next.js (Turbopack) 4.687s (~) 5.178s (~) 0.491s 6 1.90x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.340s (-17.7% 🟢) 3.434s (-16.7% 🟢) 1.093s 9 1.00x
▲ Vercel Express 2.813s (+17.1% 🔺) 4.043s (+3.8%) 1.230s 8 1.20x
▲ Vercel Nitro 3.182s (+13.6% 🔺) 4.544s (+18.0% 🔺) 1.362s 7 1.36x

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

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 3.982s (~) 4.873s (+6.2% 🔺) 0.891s 7 1.00x
🐘 Postgres Nitro 3.985s (+8.2% 🔺) 4.443s (+10.7% 🔺) 0.458s 7 1.00x
🌐 Redis Next.js (Turbopack) 4.222s (+6.1% 🔺) 5.011s (+12.9% 🔺) 0.789s 6 1.06x
🐘 Postgres Next.js (Turbopack) 4.247s (+0.9%) 5.017s (~) 0.769s 6 1.07x
💻 Local Next.js (Turbopack) 8.105s (-5.0% 🟢) 8.770s (-2.7%) 0.665s 4 2.04x
💻 Local Express 8.344s (+4.6%) 9.020s (+2.8%) 0.677s 4 2.10x
💻 Local Nitro 8.527s (+5.4% 🔺) 9.024s (+5.8% 🔺) 0.496s 4 2.14x
🌐 MongoDB Next.js (Turbopack) 9.772s (-1.5%) 10.351s (~) 0.579s 3 2.45x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.018s (+2.5%) 4.431s (+1.5%) 1.413s 7 1.00x
▲ Vercel Next.js (Turbopack) 3.166s (-10.5% 🟢) 4.068s (-12.7% 🟢) 0.903s 8 1.05x
▲ Vercel Express 3.436s (+9.8% 🔺) 4.715s (+8.2% 🔺) 1.279s 7 1.14x

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

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.169s (+20.3% 🔺) 1.001s (~) 0.011s (-2.6%) 1.017s (~) 0.849s 10 1.00x
🐘 Postgres Express 0.169s (-10.4% 🟢) 0.999s (~) 0.001s (+7.7% 🔺) 1.013s (~) 0.844s 10 1.00x
🌐 Redis Next.js (Turbopack) 0.174s (+36.0% 🔺) 1.000s (~) 0.001s (-7.1% 🟢) 1.008s (~) 0.833s 10 1.03x
💻 Local Nitro 0.192s (+11.3% 🔺) 1.003s (~) 0.012s (+1.8%) 1.017s (~) 0.825s 10 1.14x
💻 Local Express 0.194s (+8.8% 🔺) 1.003s (~) 0.011s (+1.8%) 1.017s (~) 0.823s 10 1.15x
🐘 Postgres Next.js (Turbopack) 0.206s (+13.7% 🔺) 1.001s (~) 0.002s (-6.3% 🟢) 1.013s (~) 0.806s 10 1.22x
🐘 Postgres Nitro 0.228s (+84.1% 🔺) 0.995s (-0.5%) 0.001s (+7.7% 🔺) 1.014s (~) 0.786s 10 1.35x
🌐 MongoDB Next.js (Turbopack) 0.496s (+3.4%) 0.954s (~) 0.001s (-13.3% 🟢) 1.010s (~) 0.513s 10 2.94x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 1.514s (-10.5% 🟢) 2.038s (-37.7% 🟢) 0.006s (+9.4% 🔺) 2.534s (-35.2% 🟢) 1.021s 10 1.00x
▲ Vercel Express 1.530s (-36.1% 🟢) 2.339s (-39.0% 🟢) 0.006s (-3.1%) 2.823s (-45.3% 🟢) 1.293s 10 1.01x
▲ Vercel Nitro 1.534s (-21.1% 🟢) 2.319s (-35.9% 🟢) 0.006s (-9.7% 🟢) 2.877s (-36.5% 🟢) 1.343s 10 1.01x

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

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Next.js (Turbopack) 7/12
🐘 Postgres Express 12/12
▲ Vercel Next.js (Turbopack) 6/12
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 9/12
Next.js (Turbopack) 🌐 Redis 5/12
Nitro 🐘 Postgres 5/12
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
Contributor

github-actions bot commented Mar 14, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
❌ ▲ Vercel Production 700 14 67 781
✅ 💻 Local Development 632 0 78 710
✅ 📦 Local Production 632 0 78 710
✅ 🐘 Local Postgres 632 0 78 710
✅ 🪟 Windows 68 0 3 71
❌ 🌍 Community Worlds 117 54 15 186
✅ 📋 Other 186 0 27 213
Total 2967 68 346 3381

❌ Failed Tests

▲ Vercel Production (14 failed)

nextjs-webpack (14 failed):

  • DurableAgent e2e core basic text response
  • DurableAgent e2e core single tool call
  • DurableAgent e2e core multiple sequential tool calls
  • DurableAgent e2e core tool error recovery
  • DurableAgent e2e onStepFinish fires constructor + stream callbacks in order with step data
  • DurableAgent e2e onFinish fires constructor + stream callbacks in order with event data
  • DurableAgent e2e instructions string instructions are passed to the model
  • DurableAgent e2e timeout completes within timeout
  • DurableAgent e2e experimental_onStart (GAP) completes but callbacks are not called (GAP)
  • DurableAgent e2e experimental_onStepStart (GAP) completes but callbacks are not called (GAP)
  • DurableAgent e2e experimental_onToolCallStart (GAP) completes but callbacks are not called (GAP)
  • DurableAgent e2e experimental_onToolCallFinish (GAP) completes but callbacks are not called (GAP)
  • DurableAgent e2e prepareCall (GAP) completes but prepareCall is not applied (GAP)
  • DurableAgent e2e tool approval (GAP) completes but needsApproval is not checked (GAP)
🌍 Community Worlds (54 failed)

mongodb (2 failed):

  • hookWorkflow is not resumable via public webhook endpoint
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously

redis (2 failed):

  • hookWorkflow is not resumable via public webhook endpoint
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously

turso (50 failed):

  • addTenWorkflow
  • addTenWorkflow
  • wellKnownAgentWorkflow (.well-known/agent)
  • should work with react rendering in step
  • promiseAllWorkflow
  • promiseRaceWorkflow
  • promiseAnyWorkflow
  • importedStepOnlyWorkflow
  • hookWorkflow
  • hookWorkflow is not resumable via public webhook endpoint
  • webhookWorkflow
  • sleepingWorkflow
  • parallelSleepWorkflow
  • nullByteWorkflow
  • workflowAndStepMetadataWorkflow
  • fetchWorkflow
  • promiseRaceStressTestWorkflow
  • 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()
  • hookCleanupTestWorkflow - hook token reuse after workflow completion
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously
  • hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars)
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument
  • closureVariableWorkflow - nested step functions with closure variables
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step
  • health check (queue-based) - workflow and step endpoints respond to health check messages
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly
  • Calculator.calculate - static workflow method using static step methods from another class
  • AllInOneService.processNumber - static workflow method using sibling static step methods
  • ChainableService.processWithThis - static step methods using this to reference the class
  • thisSerializationWorkflow - step function invoked with .call() and .apply()
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE
  • instanceMethodStepWorkflow - instance methods with "use step" directive
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context
  • stepFunctionAsStartArgWorkflow - step function reference passed as start() argument
  • cancelRun - cancelling a running workflow
  • cancelRun via CLI - cancelling a running workflow
  • 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
  • sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control)

Details by Category

❌ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 64 0 7
✅ example 64 0 7
✅ express 64 0 7
✅ fastify 64 0 7
✅ hono 64 0 7
✅ nextjs-turbopack 69 0 2
❌ nextjs-webpack 55 14 2
✅ nitro 64 0 7
✅ nuxt 64 0 7
✅ sveltekit 64 0 7
✅ vite 64 0 7
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 62 0 9
✅ express-stable 62 0 9
✅ fastify-stable 62 0 9
✅ hono-stable 62 0 9
✅ nextjs-turbopack-stable 68 0 3
✅ nextjs-webpack-stable 68 0 3
✅ nitro-stable 62 0 9
✅ nuxt-stable 62 0 9
✅ sveltekit-stable 62 0 9
✅ vite-stable 62 0 9
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 62 0 9
✅ express-stable 62 0 9
✅ fastify-stable 62 0 9
✅ hono-stable 62 0 9
✅ nextjs-turbopack-stable 68 0 3
✅ nextjs-webpack-stable 68 0 3
✅ nitro-stable 62 0 9
✅ nuxt-stable 62 0 9
✅ sveltekit-stable 62 0 9
✅ vite-stable 62 0 9
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 62 0 9
✅ express-stable 62 0 9
✅ fastify-stable 62 0 9
✅ hono-stable 62 0 9
✅ nextjs-turbopack-stable 68 0 3
✅ nextjs-webpack-stable 68 0 3
✅ nitro-stable 62 0 9
✅ nuxt-stable 62 0 9
✅ sveltekit-stable 62 0 9
✅ vite-stable 62 0 9
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 68 0 3
❌ 🌍 Community Worlds
App Passed Failed Skipped
✅ mongodb-dev 3 0 2
❌ mongodb 52 2 3
✅ redis-dev 3 0 2
❌ redis 52 2 3
✅ turso-dev 3 0 2
❌ turso 4 50 3
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 62 0 9
✅ e2e-local-postgres-nest-stable 62 0 9
✅ e2e-local-prod-nest-stable 62 0 9

📋 View full workflow run


Some E2E test jobs failed:

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

Check the workflow run for details.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a concurrency bug in @workflow/world-local event storage where concurrent wait_completed writes could create duplicate events and break replay, by adding an atomic completion-claim mechanism and regression tests.

Changes:

  • Add createWait / completeWait helpers for storage lifecycle tests.
  • Update wait_completed handling to atomically claim a completion lock file via writeExclusive.
  • Add wait lifecycle + concurrent completion regression tests and a patch changeset.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
packages/world-local/src/test-helpers.ts Adds wait-focused helper functions used by storage tests.
packages/world-local/src/storage/events-storage.ts Introduces exclusive-create lock file to prevent concurrent double-completion.
packages/world-local/src/storage.test.ts Adds wait lifecycle tests, including a concurrent completion regression test.
.changeset/fix-concurrent-wait-completed.md Declares a patch release note for the race-condition fix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines 731 to 749
@@ -732,12 +747,6 @@ export function createEventsStorage(
status: 404,
});
}
Comment on lines +725 to +729
const lockPath = taggedPath(
basedir,
'waits',
`${waitCompositeKey}.completed`,
tag
When a concurrent invocation already created a wait_completed event,
the local events array was missing it (the 409 handler just continued).
This could cause the workflow to not see the event during replay.

Re-fetch the full event log from the source of truth when any 409 is
encountered during wait completion, ensuring the events array has the
correct ordering with all events from concurrent invocations.
events.push(result.event!);
} catch (err) {
if (WorkflowAPIError.is(err) && err.status === 409) {
runtimeLogger.info('Wait already completed, skipping', {
Copy link
Member

Choose a reason for hiding this comment

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

If we see a 409 here, wouldn't it mean we can exit this flow early since there's a different flow that created the wait_completed event? 🤔 Unsure. Otherwise LGTM

Copy link
Member Author

@TooTallNate TooTallNate Mar 14, 2026

Choose a reason for hiding this comment

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

Ya I had that same optimization in mind. I kinda worry about if that earlier replay might not have the full event log though (i.e. if two steps completed ~simultaneously after the wait had elapsed, first replay only has the first step_completed event but the second one has both?)

Copy link
Member

Choose a reason for hiding this comment

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

Mmh fair. I think this is likely fine to merge and maybe only leads to addition unnecessary replays. Can we add a TODO in here to maybe log or monitor how often this happen? Might also need to re-examine with all the changes in #1338

Copy link
Member

@VaguelySerious VaguelySerious left a comment

Choose a reason for hiding this comment

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

LGTM let's add a comment or TODO though about re-examining this since it feels like a hack and it also applies to vercel/postgres world despite being a local world fix

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.

3 participants