Skip to content

feat(recovery): start the World at server boot to recover in-flight runs#2544

Open
pranaygp wants to merge 10 commits into
mainfrom
pgp/world-start-recovery
Open

feat(recovery): start the World at server boot to recover in-flight runs#2544
pranaygp wants to merge 10 commits into
mainfrom
pgp/world-start-recovery

Conversation

@pranaygp

@pranaygp pranaygp commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Closes #679
Related: #1531 (closed; same root cause)


Context

Self-hosted worlds (local, postgres) run boot-time recovery — reenqueueActiveRuns() re-enqueues pending/running runs — but it lives only inside world.start(), and nothing called world.start() at server startup. So a self-hosted server that restarted while a run was in flight (sleeping, waiting on a hook, between steps) never resumed that run without a subsequent workflow operation. The Vercel World didn't even implement start().

This wires world.start() to run once at server boot across the framework integrations, makes the Vercel World's start() an explicit no-op (push-based — VQS redelivers, no boot recovery needed), and adds an e2e test that proves recovery happens on startup with no workflow operation — and fails if the wiring is removed.

Changes

  • @workflow/core: add idempotent ensureWorldStarted() (once-per-process guard; getWorld()world.start?.()), exported from @workflow/core/runtime and workflow/runtime.
  • @workflow/world-vercel: add a no-op async start() for interface compliance; expand the World.start() contract doc (must be idempotent; may be a no-op for push-based worlds).
  • Framework startup wiring (un-gated — runs for all worlds; no-op on Vercel):
    • Next.js: workbench instrumentation.ts calls ensureWorldStarted() from workflow/runtime, guarded by NEXT_RUNTIME === 'nodejs'.
    • Nitro (covers Nitro v2/v3, Nuxt, Express/Hono/Fastify on Nitro): @workflow/nitro auto-registers a Nitro server plugin that runs at app boot — no manual wiring required. (See the "Nitro auto-start" update below for the implementation detail.)
    • SvelteKit / Nest: un-gate the existing world.start() calls (were @workflow/world-postgres-only, so local never recovered) and route through ensureWorldStarted().
    • Astro: a src/middleware.ts once-guard (Astro has no all-adapter startup hook).
  • Test: packages/core/e2e/restart-recovery.test.ts — starts a sleeping run server-side, hard-kills the server mid-sleep, restarts it, and asserts the run completes with no workflow op. Pure-reader test process; robust process-group kill. Gated by RESTART_RECOVERY_TEST=1; covers local + postgres on nextjs-turbopack.
  • CI: new e2e-restart-recovery job (matrix local + postgres; owns the server lifecycle, so it does not pre-start the server) added to the summary + e2e-required-check gates.
  • Docs: deploying/recovering-in-flight-runs.mdx (v4 + v5).

Verification

Ran the restart e2e locally, all four combinations:

World With startup wiring Without (instrumentation gutted)
local ✅ passes (~13s) ❌ run stuck running, 120s timeout
postgres ✅ passes (~14s) ❌ run stuck running, 120s timeout

The run is killed mid-sleep (~0.5s into an 8s sleep) and only recovers after restart via the startup ensureWorldStarted(). Postgres fails-without-fix because the graphile worker only auto-boots on the next enqueue, and the test issues no post-restart op. pnpm build / typecheck pass for all changed packages.

Note

The plan called for a @workflow/next register helper, but consuming it via workflow/next/instrumentation broke Turbopack (a CJS re-export double-hop hit @workflow/core/dist/runtime exports-encapsulation and dropped the named export through interop). Using ensureWorldStarted() from workflow/runtime directly is robust and consistent with how SvelteKit/Nest/Astro are wired.

Docs Preview

Page v4 v5
Recovering in-flight runs /docs/deploying/recovering-in-flight-runs /v5/docs/deploying/recovering-in-flight-runs

(Preview sits behind deployment protection — requires Vercel team access.)

Update (CI fixes)

Initial CI surfaced two issues from un-gating world.start(), both fixed:

  • world-local: initDataDir threw Invalid version string: "bundled" in bundled server builds — now skips version-compat when the version is the bundled sentinel.
  • Nitro: the auto startup plugin first hit ERR_INTERNAL_ASSERTION. It was briefly removed in favor of manual wiring, then restored with a fix — see below.

Update (Nitro auto-start, restored & fixed)

Nitro now starts the World automatically again — no manual server plugin needed.

The original auto-plugin imported the runtime via a build-time file:// URL, which collided with the bundled flow handler's require() of the same file (CJS/ESM dual-load → ERR_INTERNAL_ASSERTION, 500ing the flow route). The fix: @workflow/nitro now emits a real plugin file in the build dir that imports workflow/runtime via a bare dynamic import — mirroring a hand-written Nitro plugin — so the bundler resolves and dedupes it with the flow handler's runtime (no second physical module, no dual-load). ensureWorldStarted() caches its start promise on globalThis, so the World starts exactly once. Registration is gated off Vercel deploys (the Vercel World's start() is a no-op).

Also removes the temporary manual workbench/nitro-v3/plugins/start-pg-world.ts workaround + its config entry, and reverts the Nitro/Nuxt section of the recovery docs back to "no action required."

Verified on workbench/nitro-v3 (local world), dev and production build: world.start() runs at true boot before any request (creates the data dir with no request made), and the flow handler serves HTTP 200 with zero ERR_INTERNAL_ASSERTION after the boot plugin has loaded the runtime — the exact regression that caused the original removal. The nitro+postgres path should be confirmed green in CI.

🤖 Generated with Claude Code

Self-hosted worlds (local, postgres) run boot-time recovery
(`reenqueueActiveRuns`) inside `world.start()`, but nothing called it at
server startup — so a process that restarted mid-flight never resumed its
in-flight runs without a workflow operation.

- core: add idempotent `ensureWorldStarted()` (once-per-process), exported
  from `@workflow/core/runtime` and `workflow/runtime`.
- world-vercel: add a no-op `start()` for interface compliance (push-based;
  VQS redelivers, no boot recovery needed). Document the `start()` contract.
- framework startup wiring (un-gated; no-op on Vercel): Next workbench
  `instrumentation.ts`, a Nitro server plugin (covers express/hono/fastify/
  nuxt), un-gate SvelteKit `init` + Nest `bootstrap`, Astro middleware.
- test: kill/restart e2e proving an in-flight sleeping run resumes after a
  hard restart with no workflow op; fails if startup wiring is removed.
  Covers local + postgres. New `e2e-restart-recovery` CI job.
- docs: deploying/recovering-in-flight-runs (v4 + v5).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@pranaygp pranaygp requested a review from a team as a code owner June 20, 2026 16:27
Copilot AI review requested due to automatic review settings June 20, 2026 16:28
@changeset-bot

changeset-bot Bot commented Jun 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: aadabdd

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

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

vercel Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment Jun 21, 2026 9:21pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Jun 21, 2026 9:21pm
example-workflow Ready Ready Preview, Comment Jun 21, 2026 9:21pm
workbench-astro-workflow Ready Ready Preview, Comment Jun 21, 2026 9:21pm
workbench-express-workflow Error Error Jun 21, 2026 9:21pm
workbench-fastify-workflow Error Error Jun 21, 2026 9:21pm
workbench-hono-workflow Error Error Jun 21, 2026 9:21pm
workbench-nitro-workflow Ready Ready Preview, Comment Jun 21, 2026 9:21pm
workbench-nuxt-workflow Ready Ready Preview, Comment Jun 21, 2026 9:21pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Jun 21, 2026 9:21pm
workbench-tanstack-start-workflow Error Error Jun 21, 2026 9:21pm
workbench-vite-workflow Error Error Jun 21, 2026 9:21pm
workflow-swc-playground Ready Ready Preview, Comment Jun 21, 2026 9:21pm
workflow-tarballs Ready Ready Preview, Comment Jun 21, 2026 9:21pm
workflow-web Ready Ready Preview, Comment Jun 21, 2026 9:21pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
workflow-docs Skipped Skipped Jun 21, 2026 9:21pm

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

📊 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 (-3.2%) 1.006s (~) 0.966s 10 1.00x
💻 Local Next.js (Turbopack) 0.051s (+6.4% 🔺) 1.008s (~) 0.957s 10 1.29x
🐘 Postgres Next.js (Turbopack) 0.057s (-1.6%) 1.011s (~) 0.954s 10 1.44x
🐘 Postgres Nitro 0.062s (-1.6%) 1.012s (~) 0.950s 10 1.57x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 0.294s (-24.6% 🟢) 2.100s (-16.9% 🟢) 1.806s 10 1.00x
▲ Vercel Next.js (Turbopack) 0.465s (+43.7% 🔺) 2.594s (-7.0% 🟢) 2.129s 10 1.58x

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.090s (~) 2.006s (~) 0.917s 10 1.00x
🐘 Postgres Nitro 1.104s (~) 2.009s (~) 0.905s 10 1.01x
💻 Local Next.js (Turbopack) 1.105s (+1.2%) 2.007s (~) 0.902s 10 1.01x
🐘 Postgres Next.js (Turbopack) 1.109s (+0.6%) 2.012s (~) 0.903s 10 1.02x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.627s (+5.7% 🔺) 3.344s (-4.8%) 1.716s 10 1.00x
▲ Vercel Next.js (Turbopack) 2.412s (+52.8% 🔺) 4.358s (+17.3% 🔺) 1.946s 10 1.48x

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 10.506s (~) 11.022s (~) 0.516s 3 1.00x
🐘 Postgres Nitro 10.531s (~) 11.019s (~) 0.488s 3 1.00x
💻 Local Next.js (Turbopack) 10.550s (~) 11.024s (~) 0.474s 3 1.00x
🐘 Postgres Next.js (Turbopack) 10.605s (~) 11.019s (~) 0.414s 3 1.01x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 13.128s (+2.3%) 14.488s (-5.1% 🟢) 1.359s 3 1.00x
▲ Vercel Next.js (Turbopack) 13.827s (+4.9%) 16.014s (+4.2%) 2.187s 2 1.05x

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 13.713s (-1.4%) 14.016s (~) 0.303s 5 1.00x
💻 Local Nitro 13.746s (~) 14.030s (~) 0.284s 5 1.00x
🐘 Postgres Next.js (Turbopack) 13.834s (-0.6%) 14.020s (~) 0.185s 5 1.01x
💻 Local Next.js (Turbopack) 13.847s (+1.0%) 14.228s (+1.4%) 0.382s 5 1.01x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 19.872s (-7.6% 🟢) 21.322s (-9.2% 🟢) 1.450s 3 1.00x
▲ Vercel Next.js (Turbopack) 22.037s (+10.5% 🔺) 23.994s (+8.0% 🔺) 1.957s 3 1.11x

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 12.379s (+0.7%) 13.025s (~) 0.645s 7 1.00x
🐘 Postgres Nitro 12.446s (~) 13.022s (~) 0.576s 7 1.01x
💻 Local Next.js (Turbopack) 12.555s (+0.8%) 13.025s (~) 0.470s 7 1.01x
🐘 Postgres Next.js (Turbopack) 12.658s (+1.0%) 13.022s (~) 0.364s 7 1.02x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 24.616s (~) 26.413s (-1.1%) 1.797s 4 1.00x
▲ Vercel Next.js (Turbopack) 27.875s (+10.8% 🔺) 30.344s (+10.6% 🔺) 2.469s 4 1.13x

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.181s (+1.9%) 2.008s (~) 0.828s 15 1.00x
🐘 Postgres Nitro 1.197s (+0.7%) 2.008s (~) 0.811s 15 1.01x
💻 Local Nitro 1.213s (+0.9%) 2.006s (~) 0.794s 15 1.03x
💻 Local Next.js (Turbopack) 1.284s (-6.6% 🟢) 2.006s (~) 0.722s 15 1.09x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.586s (-2.3%) 3.728s (-16.3% 🟢) 1.143s 9 1.00x
▲ Vercel Next.js (Turbopack) 4.104s (+58.3% 🔺) 5.636s (+27.3% 🔺) 1.532s 6 1.59x

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.266s (+1.2%) 2.007s (~) 0.741s 15 1.00x
🐘 Postgres Nitro 1.291s (+1.8%) 2.007s (~) 0.716s 15 1.02x
💻 Local Nitro 1.960s (-2.7%) 2.315s (-7.7% 🟢) 0.355s 13 1.55x
💻 Local Next.js (Turbopack) 2.229s (-4.9%) 2.735s (-6.2% 🟢) 0.506s 11 1.76x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.711s (+9.0% 🔺) 4.125s (+1.7%) 1.415s 8 1.00x
▲ Vercel Next.js (Turbopack) 4.500s (+41.3% 🔺) 6.317s (+23.2% 🔺) 1.817s 5 1.66x

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.520s (+4.8%) 3.679s (~) 2.160s 9 1.00x
🐘 Postgres Next.js (Turbopack) 1.554s (+4.8%) 3.887s (~) 2.334s 8 1.02x
💻 Local Nitro 4.506s (+0.5%) 5.011s (~) 0.506s 6 2.97x
💻 Local Next.js (Turbopack) 6.185s (-7.5% 🟢) 6.614s (-10.9% 🟢) 0.428s 5 4.07x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.833s (+24.7% 🔺) 5.853s (+19.1% 🔺) 2.020s 6 1.00x
▲ Vercel Next.js (Turbopack) 4.577s (+30.7% 🔺) 6.585s (+21.2% 🔺) 2.009s 5 1.19x

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Next.js (Turbopack) 1.167s (-0.6%) 2.008s (~) 0.840s 15 1.00x
💻 Local Nitro 1.181s (-3.9%) 2.006s (~) 0.825s 15 1.01x
🐘 Postgres Nitro 1.204s (~) 2.007s (~) 0.803s 15 1.03x
💻 Local Next.js (Turbopack) 1.323s (-2.1%) 2.007s (~) 0.683s 15 1.13x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.182s (+0.7%) 3.628s (-6.4% 🟢) 1.446s 9 1.00x
▲ Vercel Next.js (Turbopack) 4.367s (+104.1% 🔺) 6.479s (+59.7% 🔺) 2.112s 5 2.00x

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.276s (~) 2.007s (~) 0.732s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.280s (+1.2%) 2.009s (-3.2%) 0.729s 15 1.00x
💻 Local Nitro 1.776s (-11.6% 🟢) 2.005s (-13.3% 🟢) 0.230s 15 1.39x
💻 Local Next.js (Turbopack) 2.191s (-9.4% 🟢) 2.917s (~) 0.726s 11 1.72x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.762s (+0.7%) 4.386s (+2.1%) 1.623s 7 1.00x
▲ Vercel Next.js (Turbopack) 4.054s (+52.2% 🔺) 5.860s (+40.8% 🔺) 1.805s 6 1.47x

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.438s (-1.2%) 3.342s (-11.1% 🟢) 1.904s 9 1.00x
🐘 Postgres Next.js (Turbopack) 1.633s (+11.2% 🔺) 3.886s (~) 2.253s 8 1.14x
💻 Local Nitro 5.095s (+3.1%) 5.850s (+6.1% 🔺) 0.755s 6 3.54x
💻 Local Next.js (Turbopack) 7.330s (+8.0% 🔺) 7.768s (+3.4%) 0.437s 4 5.10x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.064s (-30.4% 🟢) 5.047s (-23.2% 🟢) 1.983s 6 1.00x
▲ Vercel Next.js (Turbopack) 6.151s (+86.0% 🔺) 8.483s (+61.3% 🔺) 2.332s 4 2.01x

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 0.546s (-4.4%) 1.005s (-1.6%) 0.459s 60 1.00x
🐘 Postgres Next.js (Turbopack) 0.553s (-2.2%) 1.006s (-1.7%) 0.453s 60 1.01x
🐘 Postgres Nitro 0.571s (+1.5%) 1.023s (~) 0.452s 59 1.05x
💻 Local Next.js (Turbopack) 0.631s (+3.2%) 1.022s (~) 0.391s 59 1.16x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.304s (+9.7% 🔺) 4.881s (-3.3%) 1.578s 13 1.00x
▲ Vercel Next.js (Turbopack) 4.481s (+8.4% 🔺) 6.342s (+1.6%) 1.861s 10 1.36x

🔍 Observability: 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 🥇 Nitro 1.311s (+2.9%) 2.030s (+1.1%) 0.718s 45 1.00x
🐘 Postgres Next.js (Turbopack) 1.345s (+5.5% 🔺) 2.030s (+1.1%) 0.684s 45 1.03x
💻 Local Nitro 1.393s (+0.7%) 2.028s (+1.1%) 0.635s 45 1.06x
💻 Local Next.js (Turbopack) 1.529s (+3.6%) 2.007s (~) 0.478s 45 1.17x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 7.046s (-7.7% 🟢) 8.583s (-9.6% 🟢) 1.537s 11 1.00x
▲ Vercel Next.js (Turbopack) 10.096s (+23.3% 🔺) 11.931s (+19.3% 🔺) 1.835s 8 1.43x

🔍 Observability: 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 🥇 Nitro 2.615s (-1.4%) 3.085s (-1.7%) 0.469s 39 1.00x
🐘 Postgres Next.js (Turbopack) 2.669s (+4.7%) 3.009s (~) 0.340s 40 1.02x
💻 Local Nitro 3.036s (+1.3%) 3.523s (+2.5%) 0.487s 35 1.16x
💻 Local Next.js (Turbopack) 3.311s (+3.3%) 4.010s (+0.8%) 0.699s 30 1.27x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 13.131s (-21.4% 🟢) 15.423s (-19.0% 🟢) 2.292s 8 1.00x
▲ Vercel Next.js (Turbopack) 19.957s (+23.2% 🔺) 22.408s (+21.7% 🔺) 2.450s 6 1.52x

🔍 Observability: Nitro | 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.179s (~) 1.006s (~) 0.828s 60 1.00x
🐘 Postgres Nitro 0.221s (+4.1%) 1.006s (~) 0.785s 60 1.23x
💻 Local Nitro 0.339s (+0.5%) 1.005s (~) 0.665s 60 1.90x
💻 Local Next.js (Turbopack) 0.630s (+4.1%) 1.005s (~) 0.375s 60 3.53x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.279s (-6.5% 🟢) 2.620s (-16.0% 🟢) 1.341s 24 1.00x
▲ Vercel Next.js (Turbopack) 3.119s (+137.0% 🔺) 4.890s (+64.4% 🔺) 1.771s 13 2.44x

🔍 Observability: 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 🥇 Next.js (Turbopack) 0.288s (+7.4% 🔺) 1.006s (~) 0.718s 90 1.00x
🐘 Postgres Nitro 0.328s (+1.6%) 1.006s (~) 0.678s 90 1.14x
💻 Local Nitro 1.900s (-6.2% 🟢) 2.404s (-2.6%) 0.504s 38 6.60x
💻 Local Next.js (Turbopack) 2.843s (+6.6% 🔺) 3.454s (+12.3% 🔺) 0.611s 27 9.88x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.908s (+12.5% 🔺) 3.587s (~) 1.679s 26 1.00x
▲ Vercel Next.js (Turbopack) 3.473s (+54.9% 🔺) 5.878s (+40.4% 🔺) 2.405s 16 1.82x

🔍 Observability: Nitro | Next.js (Turbopack)

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.517s (+7.3% 🔺) 2.985s (-1.6%) 2.467s 41 1.00x
🐘 Postgres Nitro 0.547s (+4.3%) 1.077s (+2.6%) 0.530s 112 1.06x
💻 Local Nitro 9.140s (-2.3%) 9.872s (-2.4%) 0.732s 13 17.67x
💻 Local Next.js (Turbopack) 10.375s (+3.1%) 11.394s (+2.5%) 1.019s 11 20.06x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.793s (+26.8% 🔺) 4.870s (+18.5% 🔺) 2.077s 25 1.00x
▲ Vercel Next.js (Turbopack) 4.903s (+38.4% 🔺) 7.297s (+34.7% 🔺) 2.393s 17 1.76x

🔍 Observability: Nitro | 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 🥇 Nitro 1.147s (~) 2.005s (~) 0.010s (+13.0% 🔺) 2.018s (~) 0.871s 10 1.00x
💻 Local Next.js (Turbopack) 1.155s (~) 2.003s (~) 0.013s (+4.0%) 2.020s (~) 0.865s 10 1.01x
🐘 Postgres Next.js (Turbopack) 1.162s (-0.7%) 2.000s (~) 0.001s (+16.7% 🔺) 2.010s (~) 0.848s 10 1.01x
🐘 Postgres Nitro 1.166s (~) 1.996s (~) 0.001s (-31.6% 🟢) 2.010s (~) 0.844s 10 1.02x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.244s (+4.1%) 3.514s (+3.0%) 1.839s (+15.5% 🔺) 5.758s (+3.2%) 3.515s 10 1.00x
▲ Vercel Next.js (Turbopack) 4.326s (+105.0% 🔺) 4.557s (+34.7% 🔺) 2.028s (+22.2% 🔺) 8.251s (+49.0% 🔺) 3.925s 10 1.93x

🔍 Observability: Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.540s (-2.4%) 2.009s (~) 0.014s (+13.5% 🔺) 2.026s (~) 0.485s 30 1.00x
🐘 Postgres Nitro 1.575s (-4.1%) 2.005s (-1.6%) 0.005s (-2.0%) 2.025s (-1.6%) 0.450s 30 1.02x
🐘 Postgres Next.js (Turbopack) 1.631s (+1.9%) 2.010s (~) 0.005s (+4.7%) 2.026s (~) 0.395s 30 1.06x
💻 Local Next.js (Turbopack) 1.653s (+2.1%) 2.042s (+1.7%) 0.013s (+5.8% 🔺) 2.060s (+1.7%) 0.407s 30 1.07x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 6.633s (+15.9% 🔺) 7.894s (+4.2%) 0.272s (-12.3% 🟢) 8.597s (+2.1%) 1.964s 7 1.00x
▲ Vercel Next.js (Turbopack) 11.697s (+96.9% 🔺) 12.732s (+67.5% 🔺) 0.232s (-29.8% 🟢) 14.295s (+68.9% 🔺) 2.599s 5 1.76x

🔍 Observability: Nitro | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.741s (-3.6%) 1.083s (-1.9%) 0.000s (-35.7% 🟢) 1.099s (-1.7%) 0.358s 56 1.00x
🐘 Postgres Next.js (Turbopack) 0.843s (+10.7% 🔺) 1.091s (~) 0.000s (+314.5% 🔺) 1.098s (~) 0.255s 55 1.14x
💻 Local Nitro 1.298s (-4.9%) 2.013s (~) 0.000s (-41.7% 🟢) 2.015s (~) 0.717s 30 1.75x
💻 Local Next.js (Turbopack) 1.514s (+4.5%) 2.012s (~) 0.000s (~) 2.016s (~) 0.502s 30 2.04x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.374s (-29.0% 🟢) 4.586s (-30.4% 🟢) 0.000s (+Infinity% 🔺) 4.969s (-30.2% 🟢) 1.595s 13 1.00x
▲ Vercel Next.js (Turbopack) 5.482s (+29.6% 🔺) 6.151s (-1.0%) 0.000s (-100.0% 🟢) 7.357s (+8.0% 🔺) 1.876s 9 1.62x

🔍 Observability: Nitro | 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
🐘 Postgres 🥇 Nitro 1.674s (+1.1%) 2.340s (+5.7% 🔺) 0.000s (-100.0% 🟢) 2.355s (+5.6% 🔺) 0.682s 26 1.00x
🐘 Postgres Next.js (Turbopack) 2.024s (+6.0% 🔺) 2.543s (~) 0.000s (+Infinity% 🔺) 2.551s (~) 0.527s 24 1.21x
💻 Local Nitro 3.454s (-1.3%) 4.026s (+1.6%) 0.001s (-34.8% 🟢) 4.030s (+1.4%) 0.575s 15 2.06x
💻 Local Next.js (Turbopack) 4.012s (-2.6%) 4.524s (-1.6%) 0.001s (-16.7% 🟢) 4.531s (-1.7%) 0.519s 14 2.40x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 4.444s (-21.9% 🟢) 5.507s (-22.9% 🟢) 0.000s (-73.3% 🟢) 6.021s (-22.0% 🟢) 1.577s 10 1.00x
▲ Vercel Next.js (Turbopack) 8.029s (+51.6% 🔺) 8.819s (+29.9% 🔺) 0.000s (NaN%) 10.225s (+39.7% 🔺) 2.196s 6 1.81x

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Nitro 21/21
🐘 Postgres Nitro 12/21
▲ Vercel Nitro 21/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

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

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 942 0 122 1064
✅ 💻 Local Development 1401 0 119 1520
❌ 📦 Local Production 1274 127 119 1520
❌ 🐘 Local Postgres 1265 126 129 1520
✅ 🪟 Windows 152 0 0 152
✅ 📋 Other 384 0 76 460
Total 5418 253 565 6236

❌ Failed Tests

📦 Local Production (127 failed)

nuxt-stable (127 failed):

  • addTenWorkflow | wrun_01KVP0NXAAMS88K77RFJP4K397
  • addTenWorkflow | wrun_01KVP0NXAAMS88K77RFJP4K397
  • deploymentId: 'latest' is a no-op in non-Vercel worlds
  • promiseAllWorkflow | wrun_01KVP0P2GN9GB45E5P06Y7A27W
  • promiseRaceWorkflow | wrun_01KVP0P64NHQJ03HJVGTVN2BNK
  • promiseAnyWorkflow | wrun_01KVP0PVCQFDEMJ955HRPPC2PP
  • readableStreamWorkflow | wrun_01KVP0Q3D8B0NTEBQE5HJCPC8N
  • hookWorkflow | wrun_01KVP0QCYNSJHAGZKWTCDVATBN
  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KVP0QM731E0A7VTR5B2XJW12
  • webhookWorkflow | wrun_01KVP0QRNAFBRPDQE9RSVQCQCV
  • parallelStepsThenWebhookWorkflow - no hook_conflict from same-tick replay race | wrun_01KVP0QWVAMPJDNNKTPGRGG96Z
  • webhook route with invalid token
  • sleepingWorkflow | wrun_01KVP0RMMWMX0K1A71XHCXSY3Y
  • parallelSleepWorkflow | wrun_01KVP0S27QYEPRGRT1FAVB58ZY
  • sleepWinsRaceWorkflow | wrun_01KVP0S6QEMMCPR7B3RRT69MC9
  • stepWinsRaceWorkflow | wrun_01KVP0SA0KD0W5STN3D75AMH08
  • nullByteWorkflow | wrun_01KVP0SDZ4TSSXQNEKHMP9M5RX
  • workflowAndStepMetadataWorkflow | wrun_01KVP0SG5MTQVPJ72T8AB0J2KK
  • outputStreamWorkflow no startIndex (reads all chunks)
  • outputStreamWorkflow positive startIndex (skips first chunk)
  • outputStreamWorkflow negative startIndex (reads from end)
  • outputStreamWorkflow - getTailIndex and getChunks getTailIndex returns correct index after stream completes
  • outputStreamWorkflow - getTailIndex and getChunks getTailIndex returns -1 before any chunks are written
  • outputStreamWorkflow - getTailIndex and getChunks getChunks returns same content as reading the stream
  • outputStreamInsideStepWorkflow - getWritable() called inside step functions | wrun_01KVP0VF0JV28YCJT90F7V0NFG
  • utf8StreamWorkflow | wrun_01KVP0VT55HMJ3ENYH1GJ74P16
  • writableForwardedFromWorkflowWorkflow | wrun_01KVP0VYC7B16V886NTTADWKDX
  • writableForwardedFromStepWorkflow | wrun_01KVP0W0M3X5M3DDM39QJFV33T
  • fetchWorkflow | wrun_01KVP0W2RTKEPR8B5G3J4CVZVR
  • promiseRaceStressTestWorkflow | wrun_01KVP0W4ZF44FF7PNQ6N4N6A45
  • 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 catchability step throw round-trips FatalError with cause chain to workflow catch
  • error handling catchability workflow throw round-trips FatalError + cause through run_failed event
  • error handling catchability workflow throw of a non-Error value round-trips verbatim as cause
  • error handling catchability step throw of a non-Error value preserves it as cause on the wrapping FatalError
  • 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
  • stepDirectCallWorkflow - calling step functions directly outside workflow context
  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_01KVP0ZSPHZP5GZRMFF9AJ43HC
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KVP104200085RP07HZ3HHC22
  • hookGetConflictWorkflow - awaiting hook.getConflict() registers hook without payload | wrun_01KVP10EGV4N24ANYA87QYTQS8
  • 'hookGetConflictWithPriorStepWorkflow' - hook.getConflict() does not block step execution | wrun_01KVP10GVG1WEKM77NEDXSSS2V
  • 'hookGetConflictWithParallelStepWorkfl…' - hook.getConflict() does not block step execution | wrun_01KVP10KAWWNSRCQYW49ZRNBYN
  • hookGetConflictThenStepParallelWorkflow - hook.getConflict() continuation step runs alongside other steps | wrun_01KVP10PXYS552S7KVA1DDQW4H
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_01KVP115APBMBZS1TBWFCZQ0NF
  • hookClaimOnlyMutexWorkflow - hook works as a pure run mutex without payload data | wrun_01KVP120667Y4J9ZXXCW5E179T
  • hookAdoptOwnerResultWorkflow - duplicate adopts the owner result via conflict.returnValue | wrun_01KVP124NMYS5P1T1PJQ675H66
  • hookSignalOwnerWorkflow - duplicate forwards its payload to the owner via resumeHook | wrun_01KVP12AVTR97MWB6TG1N97F4S
  • hookSupersedeOwnerWorkflow - duplicate cancels the owner and claims the released token | wrun_01KVP12KNTNT16N7TWTXG47N7F
  • resume-or-start route pattern - resumeHook retried after start() reaches the new run | wrun_01KVP12VDRMC9MSPMACV4RBZ8W
  • hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running | wrun_01KVP13375HX7EX9XPJS8K0X15
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars) | wrun_01KVP13H73ZE2KRPE7WKTGTEV4
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument | wrun_01KVP13RK0WAJRMJP956JD4FFE
  • closureVariableWorkflow - nested step functions with closure variables | wrun_01KVP13Y4K0NY8F2TEYWRFQ3B0
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step | wrun_01KVP140BMSVYQGB04WAFGJ7PB
  • runClassSerializationWorkflow - Run instances serialize across workflow/step boundaries | wrun_01KVP148TSEGGTCVWK7HX890WB
  • startFromWorkflow - calling start() directly inside a workflow function with hook communication | wrun_01KVP14J23S04AHZKEA9MXNDT4
  • fibonacciWorkflow - recursive workflow composition via start() | wrun_01KVP14M042JY2WC0MK9RSGG94
  • health check endpoint (HTTP) - workflow endpoint responds to __health query parameter
  • health check (queue-based) - workflow endpoint responds to health check messages
  • health check (CLI) - workflow health command reports healthy endpoints
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly | wrun_01KVP152H4M278HAN0J6WKZNEA
  • Calculator.calculate - static workflow method using static step methods from another class | wrun_01KVP1576YPAWAZR7EFEBPDB0R
  • AllInOneService.processNumber - static workflow method using sibling static step methods | wrun_01KVP15C0M4S0Z35NZZWT1XT6B
  • ChainableService.processWithThis - static step methods using this to reference the class | wrun_01KVP15GT3089J3J47EPDMEHGC
  • thisSerializationWorkflow - step function invoked with .call() and .apply() | wrun_01KVP15NHKN1TYHJTSDV4Q05VJ
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE | wrun_01KVP15T7W93J045277EVBK58V
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_01KVP15Z37Q0GS2CCH3KSQCF5X
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context | wrun_01KVP167PNXB7DYTH5G98S98RB
  • errorSubclassRoundTripWorkflow - first-class Error subclasses survive every serialization boundary | wrun_01KVP16DPC507Y77V155WQE6SN
  • stepFunctionAsStartArgWorkflow - step function reference passed as start() argument | wrun_01KVP16FWKEEAHWAM9Q7VTBK4T
  • cancelRun - cancelling a running workflow | wrun_01KVP16MN89QQYDGBDRHSFZ38P
  • cancelRun via CLI - cancelling a running workflow | wrun_01KVP16X5D9Q0JBQD6XKSZDERS
  • hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep | wrun_01KVP177TQ727TTR1W18SRZ2S2
  • hookWithSleepFinalStepWorkflow - step only on final payload | wrun_01KVP17QC24KM5BCWT505PJ3XM
  • sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration | wrun_01KVP187DVHFAMP9G846WC8BJC
  • sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) | wrun_01KVP18GN4CP62WEJ4F6WNY6SF
  • AbortController abortTimeoutWorkflow: timeout cancels long-running step
  • AbortController abortParallelWorkflow: abort cancels all parallel steps
  • AbortController abortFromStepWorkflow: step abort cancels an in-flight sibling step
  • AbortController abortAlreadyAbortedWorkflow: pre-aborted signal seen by step
  • AbortController abortReasonWorkflow: abort reason preserved across boundaries
  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortViaHookWorkflow: external hook triggers abort on in-flight step
  • AbortController abortExternalSignalWorkflow: signal passed as workflow input
  • AbortController abortExternalSignalInFlightWorkflow: external abort fires mid-flight, propagates to nested steps
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM
  • AbortController abortAnyInStepWorkflow: AbortSignal.any inside a step composes deserialized signals
  • AbortController abortSurvivesReplayWorkflow: controller state consistent across replay
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries
  • AbortController abortReasonTypesWorkflow: various abort reason types propagate correctly
  • AbortController abortFetchUncaughtWorkflow: uncaught fetch AbortError is FatalError, no retries
  • AbortController abortFetchInFlightWorkflow: aborting cancels an in-flight fetch
  • AbortController abortVoidSleepTimeoutWorkflow: documented void sleep().then(abort) pattern works
  • AbortController abortDeterministicBranchWorkflow: if-check takes same path on first-run and replay
  • AbortController abortListenerWorkflow: signal.addEventListener fires on the deserialized step signal
  • AbortController abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step
  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [hook-first-hook-first]: hook.then → addEventListener → resumeHook → abort()
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_01KVP1D367V6K85HSGYFFX17M4
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_01KVP1D5963M45FFX32DQ1HX6W
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_01KVP1D7C6T54BHC978CCVFEWJ
  • getterStepWorkflow - getter functions with "use step" directive | wrun_01KVP1DAQP2YFTVQS3BQR0TQVZ
  • distributedAbortController - manual abort triggers signal | wrun_01KVP1DGT2E3NCN13AM0235N6F
  • distributedAbortController - TTL expiration triggers signal | wrun_01KVP1DM7RXBRC5YC2CEJ75K9J
  • distributedAbortController - reconnect to existing controller | wrun_01KVP1DRXM27590W3S18RJMWAR
  • experimental_setAttributes start: initial attributes are seeded on run creation
  • experimental_setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • experimental_setAttributes experimentalSetAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • experimental_setAttributes experimentalSetAttributesInsideStepWorkflow: step-body calls append attributed native events
  • experimental_setAttributes fire-and-forget: void experimental_setAttributes lands without awaiting
  • experimental_setAttributes Promise.all of disjoint-key writes: every key lands
  • experimental_setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run
  • experimental_setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit
  • experimental_setAttributes start: invalid initial attributes are rejected before a run is created
🐘 Local Postgres (126 failed)

nuxt-stable (126 failed):

  • addTenWorkflow | wrun_01KVP0NXAAMS88K77RFJP4K397
  • addTenWorkflow | wrun_01KVP0NXAAMS88K77RFJP4K397
  • deploymentId: 'latest' is a no-op in non-Vercel worlds
  • promiseAllWorkflow | wrun_01KVP0P2GN9GB45E5P06Y7A27W
  • promiseRaceWorkflow | wrun_01KVP0P64NHQJ03HJVGTVN2BNK
  • promiseAnyWorkflow | wrun_01KVP0PVCQFDEMJ955HRPPC2PP
  • readableStreamWorkflow | wrun_01KVP0Q3D8B0NTEBQE5HJCPC8N
  • hookWorkflow | wrun_01KVP0QCYNSJHAGZKWTCDVATBN
  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KVP0QM731E0A7VTR5B2XJW12
  • webhookWorkflow | wrun_01KVP0QRNAFBRPDQE9RSVQCQCV
  • webhook route with invalid token
  • sleepingWorkflow | wrun_01KVP0RMMWMX0K1A71XHCXSY3Y
  • parallelSleepWorkflow | wrun_01KVP0S27QYEPRGRT1FAVB58ZY
  • sleepWinsRaceWorkflow | wrun_01KVP0S6QEMMCPR7B3RRT69MC9
  • stepWinsRaceWorkflow | wrun_01KVP0SA0KD0W5STN3D75AMH08
  • nullByteWorkflow | wrun_01KVP0SDZ4TSSXQNEKHMP9M5RX
  • workflowAndStepMetadataWorkflow | wrun_01KVP0SG5MTQVPJ72T8AB0J2KK
  • outputStreamWorkflow no startIndex (reads all chunks)
  • outputStreamWorkflow positive startIndex (skips first chunk)
  • outputStreamWorkflow negative startIndex (reads from end)
  • outputStreamWorkflow - getTailIndex and getChunks getTailIndex returns correct index after stream completes
  • outputStreamWorkflow - getTailIndex and getChunks getTailIndex returns -1 before any chunks are written
  • outputStreamWorkflow - getTailIndex and getChunks getChunks returns same content as reading the stream
  • outputStreamInsideStepWorkflow - getWritable() called inside step functions | wrun_01KVP0VF0JV28YCJT90F7V0NFG
  • utf8StreamWorkflow | wrun_01KVP0VT55HMJ3ENYH1GJ74P16
  • writableForwardedFromWorkflowWorkflow | wrun_01KVP0VYC7B16V886NTTADWKDX
  • writableForwardedFromStepWorkflow | wrun_01KVP0W0M3X5M3DDM39QJFV33T
  • fetchWorkflow | wrun_01KVP0W2RTKEPR8B5G3J4CVZVR
  • promiseRaceStressTestWorkflow | wrun_01KVP0W4ZF44FF7PNQ6N4N6A45
  • 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 catchability step throw round-trips FatalError with cause chain to workflow catch
  • error handling catchability workflow throw round-trips FatalError + cause through run_failed event
  • error handling catchability workflow throw of a non-Error value round-trips verbatim as cause
  • error handling catchability step throw of a non-Error value preserves it as cause on the wrapping FatalError
  • 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
  • stepDirectCallWorkflow - calling step functions directly outside workflow context
  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_01KVP0ZSPHZP5GZRMFF9AJ43HC
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KVP104200085RP07HZ3HHC22
  • hookGetConflictWorkflow - awaiting hook.getConflict() registers hook without payload | wrun_01KVP10EGV4N24ANYA87QYTQS8
  • 'hookGetConflictWithPriorStepWorkflow' - hook.getConflict() does not block step execution | wrun_01KVP10GVG1WEKM77NEDXSSS2V
  • 'hookGetConflictWithParallelStepWorkfl…' - hook.getConflict() does not block step execution | wrun_01KVP10KAWWNSRCQYW49ZRNBYN
  • hookGetConflictThenStepParallelWorkflow - hook.getConflict() continuation step runs alongside other steps | wrun_01KVP10PXYS552S7KVA1DDQW4H
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_01KVP115APBMBZS1TBWFCZQ0NF
  • hookClaimOnlyMutexWorkflow - hook works as a pure run mutex without payload data | wrun_01KVP120667Y4J9ZXXCW5E179T
  • hookAdoptOwnerResultWorkflow - duplicate adopts the owner result via conflict.returnValue | wrun_01KVP124NMYS5P1T1PJQ675H66
  • hookSignalOwnerWorkflow - duplicate forwards its payload to the owner via resumeHook | wrun_01KVP12AVTR97MWB6TG1N97F4S
  • hookSupersedeOwnerWorkflow - duplicate cancels the owner and claims the released token | wrun_01KVP12KNTNT16N7TWTXG47N7F
  • resume-or-start route pattern - resumeHook retried after start() reaches the new run | wrun_01KVP12VDRMC9MSPMACV4RBZ8W
  • hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running | wrun_01KVP13375HX7EX9XPJS8K0X15
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars) | wrun_01KVP13H73ZE2KRPE7WKTGTEV4
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument | wrun_01KVP13RK0WAJRMJP956JD4FFE
  • closureVariableWorkflow - nested step functions with closure variables | wrun_01KVP13Y4K0NY8F2TEYWRFQ3B0
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step | wrun_01KVP140BMSVYQGB04WAFGJ7PB
  • runClassSerializationWorkflow - Run instances serialize across workflow/step boundaries | wrun_01KVP148TSEGGTCVWK7HX890WB
  • startFromWorkflow - calling start() directly inside a workflow function with hook communication | wrun_01KVP14J23S04AHZKEA9MXNDT4
  • fibonacciWorkflow - recursive workflow composition via start() | wrun_01KVP14M042JY2WC0MK9RSGG94
  • health check endpoint (HTTP) - workflow endpoint responds to __health query parameter
  • health check (queue-based) - workflow endpoint responds to health check messages
  • health check (CLI) - workflow health command reports healthy endpoints
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly | wrun_01KVP152H4M278HAN0J6WKZNEA
  • Calculator.calculate - static workflow method using static step methods from another class | wrun_01KVP1576YPAWAZR7EFEBPDB0R
  • AllInOneService.processNumber - static workflow method using sibling static step methods | wrun_01KVP15C0M4S0Z35NZZWT1XT6B
  • ChainableService.processWithThis - static step methods using this to reference the class | wrun_01KVP15GT3089J3J47EPDMEHGC
  • thisSerializationWorkflow - step function invoked with .call() and .apply() | wrun_01KVP15NHKN1TYHJTSDV4Q05VJ
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE | wrun_01KVP15T7W93J045277EVBK58V
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_01KVP15Z37Q0GS2CCH3KSQCF5X
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context | wrun_01KVP167PNXB7DYTH5G98S98RB
  • errorSubclassRoundTripWorkflow - first-class Error subclasses survive every serialization boundary | wrun_01KVP16DPC507Y77V155WQE6SN
  • stepFunctionAsStartArgWorkflow - step function reference passed as start() argument | wrun_01KVP16FWKEEAHWAM9Q7VTBK4T
  • cancelRun - cancelling a running workflow | wrun_01KVP16MN89QQYDGBDRHSFZ38P
  • cancelRun via CLI - cancelling a running workflow | wrun_01KVP16X5D9Q0JBQD6XKSZDERS
  • hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep | wrun_01KVP177TQ727TTR1W18SRZ2S2
  • hookWithSleepFinalStepWorkflow - step only on final payload | wrun_01KVP17QC24KM5BCWT505PJ3XM
  • sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration | wrun_01KVP187DVHFAMP9G846WC8BJC
  • sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) | wrun_01KVP18GN4CP62WEJ4F6WNY6SF
  • AbortController abortTimeoutWorkflow: timeout cancels long-running step
  • AbortController abortParallelWorkflow: abort cancels all parallel steps
  • AbortController abortFromStepWorkflow: step abort cancels an in-flight sibling step
  • AbortController abortAlreadyAbortedWorkflow: pre-aborted signal seen by step
  • AbortController abortReasonWorkflow: abort reason preserved across boundaries
  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortViaHookWorkflow: external hook triggers abort on in-flight step
  • AbortController abortExternalSignalWorkflow: signal passed as workflow input
  • AbortController abortExternalSignalInFlightWorkflow: external abort fires mid-flight, propagates to nested steps
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM
  • AbortController abortAnyInStepWorkflow: AbortSignal.any inside a step composes deserialized signals
  • AbortController abortSurvivesReplayWorkflow: controller state consistent across replay
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries
  • AbortController abortReasonTypesWorkflow: various abort reason types propagate correctly
  • AbortController abortFetchUncaughtWorkflow: uncaught fetch AbortError is FatalError, no retries
  • AbortController abortFetchInFlightWorkflow: aborting cancels an in-flight fetch
  • AbortController abortVoidSleepTimeoutWorkflow: documented void sleep().then(abort) pattern works
  • AbortController abortDeterministicBranchWorkflow: if-check takes same path on first-run and replay
  • AbortController abortListenerWorkflow: signal.addEventListener fires on the deserialized step signal
  • AbortController abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step
  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [hook-first-hook-first]: hook.then → addEventListener → resumeHook → abort()
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_01KVP1D367V6K85HSGYFFX17M4
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_01KVP1D5963M45FFX32DQ1HX6W
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_01KVP1D7C6T54BHC978CCVFEWJ
  • getterStepWorkflow - getter functions with "use step" directive | wrun_01KVP1DAQP2YFTVQS3BQR0TQVZ
  • distributedAbortController - manual abort triggers signal | wrun_01KVP1DGT2E3NCN13AM0235N6F
  • distributedAbortController - TTL expiration triggers signal | wrun_01KVP1DM7RXBRC5YC2CEJ75K9J
  • distributedAbortController - reconnect to existing controller | wrun_01KVP1DRXM27590W3S18RJMWAR
  • experimental_setAttributes start: initial attributes are seeded on run creation
  • experimental_setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • experimental_setAttributes experimentalSetAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • experimental_setAttributes experimentalSetAttributesInsideStepWorkflow: step-body calls append attributed native events
  • experimental_setAttributes fire-and-forget: void experimental_setAttributes lands without awaiting
  • experimental_setAttributes Promise.all of disjoint-key writes: every key lands
  • experimental_setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run
  • experimental_setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit
  • experimental_setAttributes start: invalid initial attributes are rejected before a run is created

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 125 0 27
✅ example 125 0 27
✅ nextjs-turbopack 149 0 3
✅ nextjs-webpack 149 0 3
✅ nitro 125 0 27
✅ nuxt 125 0 27
✅ sveltekit 144 0 8
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 127 0 25
✅ nextjs-turbopack-canary 133 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 152 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 152 0 0
✅ nextjs-webpack-canary 133 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 152 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 152 0 0
✅ nitro-stable 127 0 25
✅ nuxt-stable 127 0 25
✅ sveltekit-stable 146 0 6
❌ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 127 0 25
✅ nextjs-turbopack-canary 133 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 152 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 152 0 0
✅ nextjs-webpack-canary 133 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 152 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 152 0 0
✅ nitro-stable 127 0 25
❌ nuxt-stable 0 127 25
✅ sveltekit-stable 146 0 6
❌ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 126 0 26
✅ nextjs-turbopack-canary 132 0 20
✅ nextjs-turbopack-stable-lazy-discovery-disabled 151 0 1
✅ nextjs-turbopack-stable-lazy-discovery-enabled 151 0 1
✅ nextjs-webpack-canary 132 0 20
✅ nextjs-webpack-stable-lazy-discovery-disabled 151 0 1
✅ nextjs-webpack-stable-lazy-discovery-enabled 151 0 1
✅ nitro-stable 126 0 26
❌ nuxt-stable 0 126 26
✅ sveltekit-stable 145 0 7
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 152 0 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 127 0 25
✅ e2e-local-postgres-nest-stable 126 0 26
✅ e2e-local-prod-nest-stable 127 0 25
✅ e2e-restart-recovery-local 2 0 0
✅ e2e-restart-recovery-postgres 2 0 0

📋 View full workflow run


Some E2E test jobs failed:

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

Check the workflow run for details.

Comment thread packages/nitro/src/index.ts Outdated

You can call this regardless of which World you target. On the [Vercel World](/docs/deploying/world/vercel-world) it is a no-op — delivery is push-based and the queue redelivers in-flight messages on its own, so there is no long-lived process to recover.

## Wiring it per framework

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

we should also have these ben an optional accordion/compressed setup step mentioned in each of the framework's getting started guides. the step should state this this is not required for vercel deployments (serverless/push based queue worlds) but required for pull based/worker based workflow sdk deployments. and it can link to this docs pages for details

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

same for v4 and v5 docs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 6116307 — added an optional, collapsed accordion ("Recover in-flight runs after a restart") to each framework's getting-started guide (Next/Nitro/Express/Hono/Fastify/Nuxt/Vite/TanStack Start/SvelteKit/Nest/Astro, v4 + v5). Each shows the framework's startup snippet, notes it is not required for Vercel deployments, and links to the full Recovering in-flight runs page.

{
"title": "Deploying",
"pages": ["...deploying", "building-a-world"]
"pages": ["...deploying", "building-a-world", "recovering-in-flight-runs"]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

put this before "building a world"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

same for v4 and v5

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 6116307 — moved recovering-in-flight-runs before building-a-world in the Deploying nav (v4 + v5).

CI surfaced two failures from un-gating `world.start()` at server boot:

- world-local: `initDataDir` ran `parseVersion()` on the `bundled` sentinel
  that `getPackageInfo()` returns in framework server bundles, throwing
  `Invalid version string: "bundled"` and crashing startup (500s on
  sveltekit/astro/vite/etc.). Skip version-compat enforcement when the version
  is `bundled`.
- nitro: the startup plugin's bare static `import "@workflow/core/runtime"`
  couldn't be resolved by Rollup/Vite, breaking every Nitro(+Vite) build (503s
  on nitro/express/hono/fastify/tanstack-start). Resolve the runtime to a
  `file://` URL at build time and dynamic-import it (vite-/webpack-ignored),
  mirroring the dashboard handler; gate the plugin to non-Vercel builds (the
  Vercel World's start() is a no-op and the file:// path wouldn't resolve in a
  serverless function).

Verified: express (Nitro, bundled build) boots and serves the manifest 200
with no version/world-start errors.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
… order

- Move "Recovering in-flight runs" before "Building a World" in the Deploying
  nav (v4 + v5).
- Add an optional, collapsed accordion ("Recover in-flight runs after a
  restart") to each framework's getting-started guide (Next/Nitro/Express/
  Hono/Fastify/Nuxt/Vite/TanStack Start/SvelteKit/Nest/Astro, v4 + v5) with the
  framework-specific startup snippet, noting it is NOT required for Vercel
  deployments and linking to the full Recovering in-flight runs page.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

## Deploying to Production

<Accordion type="single" collapsible>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This isn't the right place. Actually let's not add anything to each individual framework getting started (the "recovering runs section" is good enough imo. instead let's just ensure that the postgres world and local world, world docs, mention that they need the world to be started at instantiation with the right framework-wide docs (just like the we did in recovering in-flight runs) along with the code snippet since only those worlds need it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 54b5b2e. Reverted the per-framework getting-started accordions, and instead added a "Starting the World" note to the local-world and postgres-world docs (only the self-hosted worlds need it): each states the World must be started at server boot, shows the ensureWorldStarted() snippet, and links to Recovering in-flight runs for per-framework wiring. Also consolidated postgres-world's prior per-framework world.start() tabs into that single snippet + link (now using the idempotent helper).

Per review: revert the per-framework getting-started accordions (the
Recovering in-flight runs page is enough) and instead document the
startup requirement on the worlds that actually need it.

- local-world / postgres-world: add a "Starting the World" note that the
  World must be started at server boot, with the `ensureWorldStarted()`
  snippet and a link to Recovering in-flight runs for per-framework wiring.
- postgres-world: consolidate the prior per-framework `world.start()` tabs
  into that single snippet + link (DRY; uses the idempotent helper).
- Remove the getting-started accordions added in the previous commit.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Re-introduce the dropped auto startup plugin so self-hosted Nitro apps
(Nitro v2/v3, Nuxt, Express/Hono/Fastify on Nitro) recover in-flight runs
after a restart with no manual wiring.

The previously-reverted version imported the runtime via a build-time
`file://` URL, which collided with the bundled flow handler's copy of the
same file (CJS/ESM dual-load -> ERR_INTERNAL_ASSERTION, 500ing the flow
route). This version instead emits a real plugin file in the build dir
that imports `workflow/runtime` via a *bare* dynamic import — mirroring a
hand-written Nitro plugin — so the bundler resolves and dedupes it with
the flow handler's runtime. `ensureWorldStarted()` caches its start
promise on `globalThis`, so the World starts exactly once. Gated off
Vercel deploys (the Vercel World's start() is a no-op).

Removes the manual `start-pg-world.ts` workbench workaround and updates
the recovering-in-flight-runs docs to note Nitro starts the World
automatically.

Co-Authored-By: Claude Opus 4.8 <[email protected]>

@vercel vercel Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Additional Suggestion:

Five workbench apps still reference the deleted plugins/start-pg-world.ts file, causing Nitro/Rollup UNRESOLVED_IMPORT build failures.

Fix on Vercel

pranaygp and others added 3 commits June 21, 2026 09:49
…-sleep

The existing case kills during a workflow `sleep()` — a delayed, unlocked
queue job. This adds a case that kills while a STEP is executing, so the
step's queue job is held/locked at crash time (the postgres graphile-worker
case from #679). Adds `longStepWorkflow` (one ~12s step) and a second
restart-recovery test that kills mid-step and asserts the run resumes after a
restart with no workflow op.

Verified locally on both the local and postgres worlds: the run recovers
(reenqueueActiveRuns re-drives the flow; the replayed step is re-dispatched and
graphile schedules a fresh run rather than stalling on the locked job).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The postgres-world "Starting the World" section listed "a Nitro server
plugin" as a place to call ensureWorldStarted(), which is now misleading —
@workflow/nitro starts the World automatically. Clarify that Nitro/Nuxt
apps need no manual call (consistent with the recovering-in-flight-runs
page and local-world docs).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…s it)

`@workflow/nuxt` registers `@workflow/nitro`, which now auto-starts the
World at boot. The pre-existing `server/plugins/start-pg-world.ts` called
`world.start()` directly (bypassing the `ensureWorldStarted` once-guard),
so with the auto-plugin nuxt+postgres started the World twice (double
`queue.start()`). Remove it — boot recovery is handled automatically.

Verified: nuxt dev (local world) starts the World at boot via the
auto-plugin with no manual plugin present.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
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.

sleep() is not durable across worker crashes in Postgres World

2 participants