Skip to content

Fix concurrent wait_completed race condition in world-local#1388

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

Fix concurrent wait_completed race condition in world-local#1388
TooTallNate wants to merge 3 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 17, 2026 1:11am
example-nextjs-workflow-webpack Ready Ready Preview, Comment Mar 17, 2026 1:11am
example-workflow Ready Ready Preview, Comment Mar 17, 2026 1:11am
workbench-astro-workflow Ready Ready Preview, Comment Mar 17, 2026 1:11am
workbench-express-workflow Ready Ready Preview, Comment Mar 17, 2026 1:11am
workbench-fastify-workflow Ready Ready Preview, Comment Mar 17, 2026 1:11am
workbench-hono-workflow Ready Ready Preview, Comment Mar 17, 2026 1:11am
workbench-nitro-workflow Ready Ready Preview, Comment Mar 17, 2026 1:11am
workbench-nuxt-workflow Ready Ready Preview, Comment Mar 17, 2026 1:11am
workbench-sveltekit-workflow Ready Ready Preview, Comment Mar 17, 2026 1:11am
workbench-vite-workflow Ready Ready Preview, Comment Mar 17, 2026 1:11am
workflow-docs Ready Ready Preview, Comment, Open in v0 Mar 17, 2026 1:11am
workflow-nest Ready Ready Preview, Comment Mar 17, 2026 1:11am
workflow-swc-playground Ready Ready Preview, Comment Mar 17, 2026 1:11am

@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: c3cac83

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 🥇 Express 0.040s (-11.4% 🟢) 1.005s (-2.0%) 0.965s 10 1.00x
💻 Local Nitro 0.043s (~) 1.005s (~) 0.963s 10 1.07x
💻 Local Next.js (Turbopack) 0.050s 1.005s 0.955s 10 1.26x
🌐 Redis Next.js (Turbopack) 0.056s 1.006s 0.950s 10 1.41x
🐘 Postgres Nitro 0.060s (-3.2%) 1.011s (~) 0.951s 10 1.52x
🐘 Postgres Express 0.069s (+9.2% 🔺) 1.012s (~) 0.943s 10 1.74x
🌐 MongoDB Next.js (Turbopack) 0.115s 1.008s 0.893s 10 2.89x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 0.462s (-1.2%) 2.766s (+1.4%) 2.304s 10 1.00x
▲ Vercel Nitro 0.495s (-26.7% 🟢) 2.649s (+6.8% 🔺) 2.154s 10 1.07x
▲ Vercel Next.js (Turbopack) 0.538s (-43.5% 🟢) 2.606s (-12.2% 🟢) 2.068s 10 1.16x

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

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 1.111s 2.006s 0.895s 10 1.00x
💻 Local Express 1.121s (~) 2.005s (~) 0.884s 10 1.01x
💻 Local Nitro 1.123s (~) 2.005s (~) 0.882s 10 1.01x
🌐 Redis Next.js (Turbopack) 1.128s 2.007s 0.879s 10 1.02x
🐘 Postgres Nitro 1.139s (-0.9%) 2.012s (~) 0.873s 10 1.03x
🐘 Postgres Express 1.158s (+1.3%) 2.012s (~) 0.854s 10 1.04x
🌐 MongoDB Next.js (Turbopack) 1.315s 2.009s 0.694s 10 1.18x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.027s (-22.3% 🟢) 3.892s (-17.2% 🟢) 1.864s 10 1.00x
▲ Vercel Next.js (Turbopack) 2.165s (+0.8%) 3.597s (-5.7% 🟢) 1.432s 10 1.07x
▲ Vercel Express 2.545s (+22.6% 🔺) 4.189s (+15.0% 🔺) 1.645s 10 1.26x

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

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 10.812s 11.024s 0.212s 3 1.00x
💻 Local Next.js (Turbopack) 10.838s 11.024s 0.185s 3 1.00x
💻 Local Nitro 10.898s (~) 11.023s (~) 0.125s 3 1.01x
💻 Local Express 10.899s (~) 11.023s (~) 0.124s 3 1.01x
🐘 Postgres Nitro 10.904s (~) 11.039s (~) 0.135s 3 1.01x
🐘 Postgres Express 10.923s (~) 11.043s (~) 0.120s 3 1.01x
🌐 MongoDB Next.js (Turbopack) 12.294s 13.024s 0.730s 3 1.14x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 16.570s (-2.2%) 18.028s (-1.3%) 1.458s 2 1.00x
▲ Vercel Next.js (Turbopack) 16.899s (-3.6%) 18.788s (-2.2%) 1.889s 2 1.02x
▲ Vercel Nitro 17.663s (+1.0%) 19.093s (-6.0% 🟢) 1.430s 2 1.07x

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

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 26.940s 27.388s 0.448s 3 1.00x
🐘 Postgres Nitro 27.073s (~) 27.725s (-1.2%) 0.652s 3 1.00x
💻 Local Next.js (Turbopack) 27.102s 28.053s 0.951s 3 1.01x
🐘 Postgres Express 27.208s (~) 28.065s (~) 0.857s 3 1.01x
💻 Local Nitro 27.446s (~) 28.053s (~) 0.607s 3 1.02x
💻 Local Express 27.516s (~) 28.054s (~) 0.538s 3 1.02x
🌐 MongoDB Next.js (Turbopack) 30.549s 31.046s 0.497s 2 1.13x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 43.830s (~) 45.404s (-2.3%) 1.574s 2 1.00x
▲ Vercel Nitro 44.103s (+1.3%) 45.430s (~) 1.327s 2 1.01x
▲ Vercel Express 44.114s (+0.6%) 45.883s (~) 1.769s 2 1.01x

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

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 53.541s 54.102s 0.561s 2 1.00x
🐘 Postgres Nitro 54.041s (~) 54.099s (-1.8%) 0.058s 2 1.01x
🐘 Postgres Express 54.228s (~) 55.102s (~) 0.874s 2 1.01x
💻 Local Next.js (Turbopack) 55.932s 56.101s 0.168s 2 1.04x
💻 Local Nitro 56.614s (~) 57.102s (~) 0.488s 2 1.06x
💻 Local Express 56.742s (~) 57.103s (~) 0.362s 2 1.06x
🌐 MongoDB Next.js (Turbopack) 60.706s 61.069s 0.363s 2 1.13x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 91.182s (-4.8%) 92.485s (-5.5% 🟢) 1.303s 1 1.00x
▲ Vercel Express 92.565s (~) 94.790s (+0.6%) 2.225s 1 1.02x
▲ Vercel Next.js (Turbopack) 96.993s (+0.6%) 99.223s (+0.7%) 2.230s 1 1.06x

🔍 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.299s 2.007s 0.708s 15 1.00x
🐘 Postgres Express 1.415s (-4.6%) 2.011s (~) 0.596s 15 1.09x
🐘 Postgres Nitro 1.416s (-1.8%) 2.011s (~) 0.594s 15 1.09x
💻 Local Nitro 1.500s (+0.6%) 2.005s (~) 0.505s 15 1.16x
💻 Local Next.js (Turbopack) 1.523s 2.005s 0.482s 15 1.17x
💻 Local Express 1.558s (+5.3% 🔺) 2.006s (~) 0.447s 15 1.20x
🌐 MongoDB Next.js (Turbopack) 2.129s 3.008s 0.879s 10 1.64x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.388s (-11.8% 🟢) 4.451s (+1.5%) 2.063s 7 1.00x
▲ Vercel Express 2.433s (+6.3% 🔺) 3.855s (-1.6%) 1.421s 8 1.02x
▲ Vercel Next.js (Turbopack) 2.588s (+2.9%) 3.990s (-2.6%) 1.402s 8 1.08x

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

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 2.534s (-2.2%) 3.013s (~) 0.478s 10 1.00x
🐘 Postgres Express 2.575s (-1.0%) 3.013s (~) 0.438s 10 1.02x
🌐 Redis Next.js (Turbopack) 2.585s 3.009s 0.424s 10 1.02x
💻 Local Next.js (Turbopack) 2.667s 3.008s 0.341s 10 1.05x
💻 Local Nitro 2.919s (+2.1%) 3.208s (-4.0%) 0.289s 10 1.15x
💻 Local Express 2.976s (+0.7%) 3.453s (+4.4%) 0.477s 9 1.17x
🌐 MongoDB Next.js (Turbopack) 4.739s 5.178s 0.439s 6 1.87x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.678s (~) 4.209s (-9.2% 🟢) 1.531s 8 1.00x
▲ Vercel Express 2.707s (+7.4% 🔺) 3.971s (-9.5% 🟢) 1.264s 8 1.01x
▲ Vercel Nitro 2.719s (+1.3%) 4.404s (+5.7% 🔺) 1.685s 7 1.02x

🔍 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 🥇 Nitro 3.989s (~) 4.589s (-3.0%) 0.600s 7 1.00x
🐘 Postgres Express 4.161s (+4.2%) 4.446s (~) 0.286s 7 1.04x
🌐 Redis Next.js (Turbopack) 4.200s 5.011s 0.812s 6 1.05x
💻 Local Express 7.942s (-2.6%) 8.519s (-2.9%) 0.577s 4 1.99x
💻 Local Next.js (Turbopack) 7.982s 8.516s 0.533s 4 2.00x
💻 Local Nitro 8.643s (+5.9% 🔺) 9.024s (~) 0.381s 4 2.17x
🌐 MongoDB Next.js (Turbopack) 9.999s 10.685s 0.686s 3 2.51x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.931s (-14.7% 🟢) 4.124s (-19.8% 🟢) 1.193s 8 1.00x
▲ Vercel Next.js (Turbopack) 3.257s (-14.3% 🟢) 5.458s (-1.2%) 2.201s 6 1.11x
▲ Vercel Nitro 3.512s (+14.8% 🔺) 5.562s (+21.6% 🔺) 2.050s 6 1.20x

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

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 1.275s 2.007s 0.732s 15 1.00x
🐘 Postgres Nitro 1.389s (-2.3%) 2.011s (~) 0.622s 15 1.09x
🐘 Postgres Express 1.401s (+1.3%) 2.011s (~) 0.610s 15 1.10x
💻 Local Next.js (Turbopack) 1.506s 2.005s 0.499s 15 1.18x
💻 Local Nitro 1.527s (~) 2.006s (~) 0.479s 15 1.20x
💻 Local Express 1.536s (~) 2.006s (~) 0.470s 15 1.21x
🌐 MongoDB Next.js (Turbopack) 2.212s 3.009s 0.797s 10 1.73x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.095s (-6.1% 🟢) 3.884s (+3.0%) 1.788s 8 1.00x
▲ Vercel Nitro 2.103s (-10.8% 🟢) 3.797s (-16.5% 🟢) 1.694s 8 1.00x
▲ Vercel Express 2.152s (-17.5% 🟢) 3.960s (-11.1% 🟢) 1.807s 8 1.03x

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

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.597s (+3.2%) 3.012s (~) 0.415s 10 1.00x
🐘 Postgres Nitro 2.626s (-1.4%) 3.011s (-3.3%) 0.385s 10 1.01x
🌐 Redis Next.js (Turbopack) 2.630s 3.008s 0.379s 10 1.01x
💻 Local Next.js (Turbopack) 2.781s 3.107s 0.327s 10 1.07x
💻 Local Express 2.895s (-3.2%) 3.342s (-6.3% 🟢) 0.446s 9 1.12x
💻 Local Nitro 2.993s (-1.9%) 3.455s (-11.1% 🟢) 0.462s 9 1.15x
🌐 MongoDB Next.js (Turbopack) 4.747s 5.178s 0.432s 6 1.83x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.469s (+2.9%) 3.867s (-4.6%) 1.398s 8 1.00x
▲ Vercel Next.js (Turbopack) 2.703s (+8.2% 🔺) 4.823s (+21.2% 🔺) 2.120s 7 1.10x
▲ Vercel Express 2.863s (+3.6%) 4.140s (+1.0%) 1.277s 8 1.16x

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

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 3.940s (-3.6%) 4.446s (~) 0.506s 7 1.00x
🐘 Postgres Express 3.975s (+0.6%) 4.445s (-3.1%) 0.470s 7 1.01x
🌐 Redis Next.js (Turbopack) 4.204s 5.012s 0.808s 6 1.07x
💻 Local Nitro 8.447s (-9.1% 🟢) 9.023s (-9.9% 🟢) 0.576s 4 2.14x
💻 Local Express 8.534s (-2.2%) 9.021s (~) 0.487s 4 2.17x
💻 Local Next.js (Turbopack) 8.570s 9.017s 0.447s 4 2.18x
🌐 MongoDB Next.js (Turbopack) 10.147s 10.685s 0.538s 3 2.58x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.683s (+1.1%) 4.105s (+5.3% 🔺) 1.422s 8 1.00x
▲ Vercel Express 3.021s (+9.4% 🔺) 4.308s (+6.5% 🔺) 1.288s 7 1.13x
▲ Vercel Next.js (Turbopack) 3.105s (-12.7% 🟢) 5.640s (+3.3%) 2.535s 6 1.16x

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

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.167s 1.002s 0.012s 1.018s 0.850s 10 1.00x
🌐 Redis Next.js (Turbopack) 0.184s 1.000s 0.002s 1.008s 0.824s 10 1.10x
💻 Local Express 0.198s (+1.7%) 1.003s (~) 0.013s (+9.6% 🔺) 1.018s (~) 0.820s 10 1.18x
💻 Local Nitro 0.199s (~) 1.003s (~) 0.011s (-2.6%) 1.017s (~) 0.819s 10 1.19x
🐘 Postgres Nitro 0.214s (-3.1%) 0.994s (~) 0.002s (+23.1% 🔺) 1.013s (~) 0.799s 10 1.28x
🐘 Postgres Express 0.225s (+1.6%) 0.995s (~) 0.002s (+33.3% 🔺) 1.013s (~) 0.788s 10 1.35x
🌐 MongoDB Next.js (Turbopack) 0.500s 0.950s 0.002s 1.009s 0.509s 10 2.99x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 1.580s (-16.5% 🟢) 2.616s (+14.8% 🔺) 0.006s (-6.3% 🟢) 3.429s (+7.9% 🔺) 1.848s 10 1.00x
▲ Vercel Next.js (Turbopack) 1.667s (-2.9%) 2.616s (-3.6%) 0.177s (+3589.6% 🔺) 3.362s (-4.3%) 1.695s 10 1.05x
▲ Vercel Nitro 1.817s (+13.9% 🔺) 3.156s (+17.2% 🔺) 0.006s (-95.7% 🟢) 3.710s (+8.5% 🔺) 1.893s 10 1.15x

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

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Next.js (Turbopack) 8/12
🐘 Postgres Nitro 10/12
▲ Vercel Nitro 5/12
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 6/12
Next.js (Turbopack) 🌐 Redis 7/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 714 0 67 781
✅ 💻 Local Development 740 0 112 852
✅ 📦 Local Production 740 0 112 852
❌ 🐘 Local Postgres 739 1 112 852
✅ 🪟 Windows 68 0 3 71
❌ 🌍 Community Worlds 116 55 15 186
✅ 📋 Other 186 0 27 213
Total 3303 56 448 3807

❌ Failed Tests

🐘 Local Postgres (1 failed)

vite-stable (1 failed):

  • webhookWorkflow
🌍 Community Worlds (55 failed)

mongodb (3 failed):

  • hookWorkflow is not resumable via public webhook endpoint
  • webhookWorkflow
  • 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 69 0 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-canary 54 0 17
✅ nextjs-turbopack-stable 68 0 3
✅ nextjs-webpack-canary 54 0 17
✅ 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-canary 54 0 17
✅ nextjs-turbopack-stable 68 0 3
✅ nextjs-webpack-canary 54 0 17
✅ 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-canary 54 0 17
✅ nextjs-turbopack-stable 68 0 3
✅ nextjs-webpack-canary 54 0 17
✅ nextjs-webpack-stable 68 0 3
✅ nitro-stable 62 0 9
✅ nuxt-stable 62 0 9
✅ sveltekit-stable 62 0 9
❌ vite-stable 61 1 9
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 68 0 3
❌ 🌍 Community Worlds
App Passed Failed Skipped
✅ mongodb-dev 3 0 2
❌ mongodb 51 3 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: success
  • Local Dev: success
  • Local Prod: success
  • Local Postgres: failure
  • 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