Skip to content

world-local: atomically dedupe duplicate step_created/wait_created events#1877

Merged
TooTallNate merged 1 commit into
mainfrom
world-local-event-uniqueness
May 4, 2026
Merged

world-local: atomically dedupe duplicate step_created/wait_created events#1877
TooTallNate merged 1 commit into
mainfrom
world-local-event-uniqueness

Conversation

@TooTallNate
Copy link
Copy Markdown
Member

Summary

Fixes a race condition in @workflow/world-local where concurrent invocations producing identical correlationIds for step_created or wait_created events would both succeed and persist duplicate events in the log.

Background

step_created previously had no atomicity guard — two concurrent calls with the same correlationId both wrote the entity and the event, leaving the second write to silently overwrite the first.

wait_created used a TOCTOU read-then-check pattern: read the existing wait, throw if found, otherwise write. Under concurrency both readers can pass the existence check before either writes.

The rest of the runtime already expects EntityConflictError to be thrown on duplicate writes (see the EntityConflictError.is(err) catch path in runtime/snapshot-entrypoint.ts), so the missing guard was a real correctness gap.

Fix

Both branches now claim a per-(runId, correlationId) constraint file under .locks/{steps,waits}/ with O_CREAT|O_EXCL semantics (via the existing writeExclusive helper used for hook tokens). The loser surfaces as EntityConflictError.

Includes 3 regression tests covering:

  • Concurrent step_created with same correlationId.
  • Concurrent wait_created with same correlationId (replaces the prior TOCTOU pattern).
  • Sequential duplicate step_created (existing pass-through behavior preserved).

Verification

pnpm -F @workflow/world-local typecheck   # clean
pnpm -F @workflow/world-local build       # clean
pnpm -F @workflow/world-local test        # 269 passed (was 266 before the 3 regression tests)

Extracted from PR #1300 (snapshot-runtime), where this fix originated. The snapshot runtime produces deterministic correlationIds across concurrent VM invocations of the same resumption by design — that path made the dedup gap reliably reproducible — but the fix is also valuable on its own for the replay runtime under any concurrent-create scenario.

Copilot AI review requested due to automatic review settings April 30, 2026 08:11
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 30, 2026

🦋 Changeset detected

Latest commit: 7f7a9ff

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

This PR includes changesets to release 19 packages
Name Type
@workflow/world-local Patch
@workflow/cli Patch
@workflow/core Patch
@workflow/vitest Patch
@workflow/world-postgres Patch
workflow Patch
@workflow/world-testing Patch
@workflow/builders Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/web-shared Patch
@workflow/web 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

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 30, 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 May 3, 2026 5:34pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment May 3, 2026 5:34pm
example-workflow Ready Ready Preview, Comment May 3, 2026 5:34pm
workbench-astro-workflow Ready Ready Preview, Comment May 3, 2026 5:34pm
workbench-express-workflow Ready Ready Preview, Comment May 3, 2026 5:34pm
workbench-fastify-workflow Ready Ready Preview, Comment May 3, 2026 5:34pm
workbench-hono-workflow Ready Ready Preview, Comment May 3, 2026 5:34pm
workbench-nitro-workflow Ready Ready Preview, Comment May 3, 2026 5:34pm
workbench-nuxt-workflow Ready Ready Preview, Comment May 3, 2026 5:34pm
workbench-sveltekit-workflow Ready Ready Preview, Comment May 3, 2026 5:34pm
workbench-vite-workflow Ready Ready Preview, Comment May 3, 2026 5:34pm
workflow-docs Ready Ready Preview, Comment, Open in v0 May 3, 2026 5:34pm
workflow-swc-playground Ready Ready Preview, Comment May 3, 2026 5:34pm
workflow-web Ready Ready Preview, Comment May 3, 2026 5:34pm

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 30, 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.043s (-1.2%) 1.006s (~) 0.963s 10 1.00x
💻 Local Express 0.043s (-3.4%) 1.005s (~) 0.962s 10 1.00x
💻 Local Next.js (Turbopack) 0.051s 1.006s 0.954s 10 1.20x
🐘 Postgres Next.js (Turbopack) 0.056s 1.010s 0.954s 10 1.31x
🐘 Postgres Express 0.058s (-0.9%) 1.010s (~) 0.953s 10 1.35x
🐘 Postgres Nitro 0.189s (+98.4% 🔺) 1.084s (+3.9%) 0.895s 10 4.43x
workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 1.123s 2.006s 0.884s 10 1.00x
💻 Local Express 1.126s (~) 2.005s (~) 0.880s 10 1.00x
💻 Local Nitro 1.128s (~) 2.006s (~) 0.879s 10 1.00x
🐘 Postgres Next.js (Turbopack) 1.141s 2.009s 0.868s 10 1.02x
🐘 Postgres Express 1.145s (~) 2.011s (~) 0.866s 10 1.02x
🐘 Postgres Nitro 1.285s (+12.7% 🔺) 2.050s (+2.0%) 0.765s 10 1.14x
workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 10.837s 11.017s 0.180s 3 1.00x
💻 Local Next.js (Turbopack) 10.845s 11.024s 0.179s 3 1.00x
🐘 Postgres Express 10.915s (~) 11.025s (~) 0.110s 3 1.01x
💻 Local Express 10.948s (~) 11.025s (~) 0.076s 3 1.01x
💻 Local Nitro 10.955s (~) 11.022s (~) 0.068s 3 1.01x
🐘 Postgres Nitro 11.967s (+10.1% 🔺) 12.354s (+12.0% 🔺) 0.387s 3 1.10x
workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 14.492s 15.020s 0.528s 4 1.00x
🐘 Postgres Express 14.647s (~) 15.026s (~) 0.379s 4 1.01x
💻 Local Next.js (Turbopack) 14.816s 15.030s 0.214s 4 1.02x
💻 Local Nitro 15.034s (~) 15.530s (-3.1%) 0.496s 4 1.04x
💻 Local Express 15.041s (~) 16.033s (+6.7% 🔺) 0.992s 4 1.04x
🐘 Postgres Nitro 15.304s (+4.9%) 16.027s (+6.6% 🔺) 0.723s 4 1.06x
workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 13.747s 14.022s 0.274s 7 1.00x
🐘 Postgres Express 14.027s (~) 14.594s (~) 0.568s 7 1.02x
🐘 Postgres Nitro 15.835s (+13.4% 🔺) 16.192s (+13.2% 🔺) 0.357s 6 1.15x
💻 Local Next.js (Turbopack) 16.260s 17.033s 0.773s 6 1.18x
💻 Local Express 16.800s (+1.2%) 17.031s (~) 0.231s 6 1.22x
💻 Local Nitro 16.929s (+0.9%) 17.366s (+2.0%) 0.437s 6 1.23x
Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.234s 2.010s 0.777s 15 1.00x
🐘 Postgres Express 1.281s (+1.6%) 2.010s (~) 0.729s 15 1.04x
🐘 Postgres Nitro 1.359s (+6.6% 🔺) 2.019s (~) 0.660s 15 1.10x
💻 Local Express 1.540s (+3.5%) 2.006s (~) 0.466s 15 1.25x
💻 Local Nitro 1.565s (-4.1%) 2.006s (-3.3%) 0.441s 15 1.27x
💻 Local Next.js (Turbopack) 1.578s 2.006s 0.427s 15 1.28x
Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.344s (-0.7%) 3.008s (~) 0.664s 10 1.00x
🐘 Postgres Next.js (Turbopack) 2.401s 3.010s 0.608s 10 1.02x
🐘 Postgres Nitro 2.422s (+3.0%) 3.011s (~) 0.589s 10 1.03x
💻 Local Next.js (Turbopack) 2.944s 3.759s 0.815s 8 1.26x
💻 Local Express 2.965s (~) 3.308s (-4.2%) 0.343s 10 1.27x
💻 Local Nitro 3.093s (-1.6%) 3.760s (-3.2%) 0.667s 8 1.32x
Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 3.463s (-0.7%) 4.013s (~) 0.549s 8 1.00x
🐘 Postgres Next.js (Turbopack) 3.661s 4.010s 0.348s 8 1.06x
🐘 Postgres Nitro 3.853s (+10.7% 🔺) 4.323s (+7.8% 🔺) 0.470s 7 1.11x
💻 Local Express 7.935s (-4.8%) 8.772s (-2.8%) 0.837s 4 2.29x
💻 Local Nitro 8.345s (~) 9.022s (~) 0.677s 4 2.41x
💻 Local Next.js (Turbopack) 8.732s 9.521s 0.789s 4 2.52x
Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.220s 2.010s 0.790s 15 1.00x
🐘 Postgres Express 1.278s (+1.7%) 2.008s (~) 0.729s 15 1.05x
🐘 Postgres Nitro 1.308s (+4.0%) 2.011s (~) 0.703s 15 1.07x
💻 Local Express 1.519s (-19.8% 🟢) 2.006s (-15.2% 🟢) 0.487s 15 1.24x
💻 Local Nitro 1.522s (-18.4% 🟢) 2.006s (-14.3% 🟢) 0.483s 15 1.25x
💻 Local Next.js (Turbopack) 1.537s 2.006s 0.470s 15 1.26x
Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.352s (~) 3.012s (~) 0.659s 10 1.00x
🐘 Postgres Next.js (Turbopack) 2.385s 3.008s 0.623s 10 1.01x
🐘 Postgres Nitro 2.574s (+10.0% 🔺) 3.011s (~) 0.437s 10 1.09x
💻 Local Next.js (Turbopack) 3.049s 3.760s 0.712s 8 1.30x
💻 Local Nitro 3.102s (+1.2%) 3.885s (~) 0.783s 8 1.32x
💻 Local Express 3.110s (-0.7%) 4.010s (+6.6% 🔺) 0.900s 8 1.32x
Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 3.489s (~) 4.014s (~) 0.525s 8 1.00x
🐘 Postgres Next.js (Turbopack) 3.650s 4.013s 0.364s 8 1.05x
🐘 Postgres Nitro 4.058s (+16.6% 🔺) 4.674s (+16.6% 🔺) 0.617s 7 1.16x
💻 Local Next.js (Turbopack) 8.353s 9.023s 0.670s 4 2.39x
💻 Local Express 8.728s (-0.8%) 9.024s (-2.7%) 0.296s 4 2.50x
💻 Local Nitro 8.898s (-2.7%) 9.522s (-5.0% 🟢) 0.624s 4 2.55x
workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.761s 1.006s 0.245s 60 1.00x
🐘 Postgres Express 0.841s (~) 1.023s (~) 0.182s 59 1.11x
💻 Local Next.js (Turbopack) 0.904s 1.058s 0.153s 57 1.19x
💻 Local Express 0.999s (+1.6%) 1.424s (+32.3% 🔺) 0.425s 43 1.31x
💻 Local Nitro 1.009s (+2.9%) 1.611s (+47.2% 🔺) 0.601s 38 1.33x
🐘 Postgres Nitro 1.166s (+42.1% 🔺) 1.664s (+65.3% 🔺) 0.498s 37 1.53x
workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.874s 2.029s 0.155s 45 1.00x
🐘 Postgres Express 1.973s (~) 2.284s (+1.2%) 0.311s 40 1.05x
🐘 Postgres Nitro 2.602s (+35.0% 🔺) 3.055s (+45.5% 🔺) 0.453s 30 1.39x
💻 Local Next.js (Turbopack) 2.833s 3.075s 0.242s 30 1.51x
💻 Local Nitro 3.028s (~) 3.730s (-0.8%) 0.702s 25 1.62x
💻 Local Express 3.052s (+1.2%) 3.885s (+8.4% 🔺) 0.833s 24 1.63x
workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 3.814s 4.043s 0.229s 30 1.00x
🐘 Postgres Express 4.045s (+1.4%) 4.626s (+5.9% 🔺) 0.581s 26 1.06x
🐘 Postgres Nitro 5.942s (+44.8% 🔺) 6.390s (+38.8% 🔺) 0.449s 19 1.56x
💻 Local Next.js (Turbopack) 8.899s 9.232s 0.334s 14 2.33x
💻 Local Nitro 9.202s (-1.0%) 9.788s (-2.3%) 0.585s 13 2.41x
💻 Local Express 9.280s (+0.8%) 9.865s (-1.5%) 0.585s 13 2.43x
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.257s 1.008s 0.751s 60 1.00x
🐘 Postgres Express 0.294s (+4.1%) 1.007s (~) 0.713s 60 1.15x
🐘 Postgres Nitro 0.346s (+22.1% 🔺) 1.034s (+2.6%) 0.688s 59 1.35x
💻 Local Next.js (Turbopack) 0.555s 1.022s 0.466s 59 2.16x
💻 Local Express 0.633s (+12.9% 🔺) 1.022s (+1.7%) 0.389s 59 2.47x
💻 Local Nitro 0.640s (+5.8% 🔺) 1.021s (~) 0.381s 59 2.49x
workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.481s 1.006s 0.525s 90 1.00x
🐘 Postgres Express 0.507s (-0.5%) 1.007s (~) 0.500s 90 1.06x
🐘 Postgres Nitro 0.673s (+35.7% 🔺) 1.164s (+15.7% 🔺) 0.491s 78 1.40x
💻 Local Express 2.512s (~) 3.009s (~) 0.497s 30 5.23x
💻 Local Nitro 2.552s (+0.6%) 3.009s (~) 0.457s 30 5.31x
💻 Local Next.js (Turbopack) 2.606s 3.009s 0.403s 30 5.42x
workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.761s 1.006s 0.245s 120 1.00x
🐘 Postgres Express 0.819s (~) 1.009s (-0.8%) 0.190s 119 1.08x
🐘 Postgres Nitro 1.197s (+51.4% 🔺) 1.627s (+61.5% 🔺) 0.431s 74 1.57x
💻 Local Next.js (Turbopack) 10.872s 11.483s 0.611s 11 14.28x
💻 Local Express 10.982s (-1.9%) 11.573s (-3.1%) 0.591s 11 14.43x
💻 Local Nitro 11.223s (~) 12.030s (+3.1%) 0.807s 10 14.75x
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.177s 1.003s 0.012s 1.018s 0.841s 10 1.00x
🐘 Postgres Next.js (Turbopack) 0.186s 1.000s 0.001s 1.009s 0.823s 10 1.05x
💻 Local Express 0.209s (+5.0%) 1.004s (~) 0.012s (-3.3%) 1.018s (~) 0.809s 10 1.18x
🐘 Postgres Express 0.214s (+4.4%) 0.998s (~) 0.002s (-6.3% 🟢) 1.011s (~) 0.797s 10 1.21x
🐘 Postgres Nitro 0.215s (+4.8%) 0.997s (~) 0.028s (+1766.7% 🔺) 1.055s (+4.4%) 0.840s 10 1.21x
💻 Local Nitro 0.218s (+2.2%) 1.004s (~) 0.012s (-1.6%) 1.018s (~) 0.800s 10 1.23x
stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.613s 1.009s 0.006s 1.024s 0.411s 59 1.00x
🐘 Postgres Express 0.623s (-1.1%) 1.007s (~) 0.004s (+5.3% 🔺) 1.023s (~) 0.400s 59 1.02x
💻 Local Express 0.751s (-0.9%) 1.011s (-1.8%) 0.010s (+6.9% 🔺) 1.023s (-1.7%) 0.272s 59 1.23x
💻 Local Next.js (Turbopack) 0.753s 1.012s 0.010s 1.118s 0.365s 54 1.23x
💻 Local Nitro 0.758s (-9.6% 🟢) 1.012s (~) 0.011s (+19.6% 🔺) 1.025s (-8.1% 🟢) 0.267s 59 1.24x
🐘 Postgres Nitro 0.820s (+31.3% 🔺) 1.274s (+26.6% 🔺) 0.029s (+602.2% 🔺) 1.325s (+29.6% 🔺) 0.506s 49 1.34x
10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 0.935s 1.177s 0.000s 1.184s 0.249s 51 1.00x
🐘 Postgres Express 0.981s (+2.1%) 1.245s (-2.6%) 0.000s (+91.7% 🔺) 1.259s (-3.6%) 0.278s 48 1.05x
💻 Local Express 1.222s (~) 2.022s (~) 0.000s (+10.0% 🔺) 2.024s (~) 0.802s 30 1.31x
💻 Local Nitro 1.237s (+1.1%) 2.022s (~) 0.000s (+200.0% 🔺) 2.024s (~) 0.787s 30 1.32x
💻 Local Next.js (Turbopack) 1.242s 2.019s 0.000s 2.022s 0.780s 30 1.33x
🐘 Postgres Nitro 1.370s (+41.4% 🔺) 1.881s (+50.8% 🔺) 0.000s (-100.0% 🟢) 1.916s (+52.3% 🔺) 0.546s 33 1.46x
fan-out fan-in 10 streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.738s (-1.9%) 2.032s (-6.7% 🟢) 0.000s (NaN%) 2.072s (-5.8% 🟢) 0.333s 29 1.00x
🐘 Postgres Next.js (Turbopack) 1.821s 2.146s 0.000s 2.154s 0.333s 28 1.05x
🐘 Postgres Nitro 2.869s (+60.1% 🔺) 3.355s (+56.7% 🔺) 0.000s (-100.0% 🟢) 3.444s (+58.4% 🔺) 0.576s 18 1.65x
💻 Local Express 2.888s (-16.7% 🟢) 3.086s (-23.5% 🟢) 0.000s (-62.5% 🟢) 3.088s (-23.5% 🟢) 0.200s 20 1.66x
💻 Local Nitro 2.918s (-13.9% 🟢) 3.192s (-20.8% 🟢) 0.001s (-1.3%) 3.195s (-20.9% 🟢) 0.276s 19 1.68x
💻 Local Next.js (Turbopack) 3.030s 3.678s 0.001s 3.682s 0.653s 17 1.74x

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Next.js (Turbopack) 13/21
🐘 Postgres Next.js (Turbopack) 16/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 18/21
Next.js (Turbopack) 🐘 Postgres 18/21
Nitro 🐘 Postgres 14/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 Apr 30, 2026

🧪 E2E Test Results

All tests passed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 1011 0 67 1078
✅ 💻 Local Development 1090 0 86 1176
✅ 📦 Local Production 1090 0 86 1176
✅ 🐘 Local Postgres 1090 0 86 1176
✅ 🪟 Windows 98 0 0 98
✅ 📋 Other 276 0 18 294
Total 4655 0 343 4998

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 91 0 7
✅ example 91 0 7
✅ express 91 0 7
✅ fastify 91 0 7
✅ hono 91 0 7
✅ nextjs-turbopack 96 0 2
✅ nextjs-webpack 96 0 2
✅ nitro 91 0 7
✅ nuxt 91 0 7
✅ sveltekit 91 0 7
✅ vite 91 0 7
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 92 0 6
✅ express-stable 92 0 6
✅ fastify-stable 92 0 6
✅ hono-stable 92 0 6
✅ nextjs-turbopack-canary 79 0 19
✅ nextjs-turbopack-stable 98 0 0
✅ nextjs-webpack-canary 79 0 19
✅ nextjs-webpack-stable 98 0 0
✅ nitro-stable 92 0 6
✅ nuxt-stable 92 0 6
✅ sveltekit-stable 92 0 6
✅ vite-stable 92 0 6
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 92 0 6
✅ express-stable 92 0 6
✅ fastify-stable 92 0 6
✅ hono-stable 92 0 6
✅ nextjs-turbopack-canary 79 0 19
✅ nextjs-turbopack-stable 98 0 0
✅ nextjs-webpack-canary 79 0 19
✅ nextjs-webpack-stable 98 0 0
✅ nitro-stable 92 0 6
✅ nuxt-stable 92 0 6
✅ sveltekit-stable 92 0 6
✅ vite-stable 92 0 6
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 92 0 6
✅ express-stable 92 0 6
✅ fastify-stable 92 0 6
✅ hono-stable 92 0 6
✅ nextjs-turbopack-canary 79 0 19
✅ nextjs-turbopack-stable 98 0 0
✅ nextjs-webpack-canary 79 0 19
✅ nextjs-webpack-stable 98 0 0
✅ nitro-stable 92 0 6
✅ nuxt-stable 92 0 6
✅ sveltekit-stable 92 0 6
✅ vite-stable 92 0 6
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 98 0 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 92 0 6
✅ e2e-local-postgres-nest-stable 92 0 6
✅ e2e-local-prod-nest-stable 92 0 6

📋 View full workflow run

Copy link
Copy Markdown
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 race in @workflow/world-local where concurrent writers could create duplicate step_created / wait_created events (and overwrite entities) when the same correlationId is produced concurrently (notably by the snapshot runtime’s deterministic correlation IDs).

Changes:

  • Add an atomic per-(runId, correlationId) constraint-file claim (via writeExclusive / O_CREAT|O_EXCL) for step_created.
  • Replace wait_created’s TOCTOU read-then-check with the same atomic constraint-file claim.
  • Add regression tests covering concurrent duplicates for steps/waits and sequential duplicate steps, plus a changeset for a patch release.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
packages/world-local/src/storage/events-storage.ts Adds atomic .locks/{steps,waits} constraint-file claims to dedupe concurrent step_created/wait_created.
packages/world-local/src/storage.test.ts Adds regression coverage for concurrent duplicate creation races and sequential duplicate step_created.
.changeset/fix-world-local-step-created-race.md Publishes a patch changeset describing the concurrency fix and behavior change.

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

Comment thread packages/world-local/src/storage/events-storage.ts Outdated
…-local

Concurrent invocations producing identical correlationIds (as the snapshot
runtime does by design across replays) previously both succeeded and
persisted duplicate events. step_created had no guard at all; wait_created
used a TOCTOU read-then-check that allowed both writers through under
concurrency. Both now claim a per-(runId, correlationId) constraint file
with O_CREAT|O_EXCL before writing, so the loser surfaces as
EntityConflictError — which the runtime's dedup catch path already
handles.
@TooTallNate TooTallNate force-pushed the world-local-event-uniqueness branch from 2a4c395 to 7f7a9ff Compare May 3, 2026 17:30
@TooTallNate TooTallNate merged commit 92dc826 into main May 4, 2026
268 of 289 checks passed
@TooTallNate TooTallNate deleted the world-local-event-uniqueness branch May 4, 2026 00:21
pranaygp added a commit that referenced this pull request May 4, 2026
…ignal

* origin/main:
  [workbench] Add TanStack Start workbench and tests (#1875)
  Atomically dedupe duplicate step_created/wait_created events in world-local (#1877)
  Split tarball hosting out of docs into its own project (#1893)
  Replace fixed-sleep hook waits with event-driven waitForHook helper (#1879)
pranaygp added a commit that referenced this pull request May 4, 2026
…lier-errors-followups

* origin-https/main:
  [workbench] Add TanStack Start workbench and tests (#1875)
  Atomically dedupe duplicate step_created/wait_created events in world-local (#1877)
  Split tarball hosting out of docs into its own project (#1893)
  Replace fixed-sleep hook waits with event-driven waitForHook helper (#1879)

# Conflicts:
#	pnpm-lock.yaml
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