Skip to content

Harden event pagination responses#2180

Merged
VaguelySerious merged 2 commits into
mainfrom
pranaygp/codex/harden-event-pagination
May 31, 2026
Merged

Harden event pagination responses#2180
VaguelySerious merged 2 commits into
mainfrom
pranaygp/codex/harden-event-pagination

Conversation

@pranaygp
Copy link
Copy Markdown
Contributor

Summary

  • deduplicate overlapping event-log pages returned during cursor pagination
  • retry a rejected continuation cursor once by reloading the event log from the beginning
  • fail with a world contract error when pagination returns no usable progress
  • prevent duplicate event IDs within incremental runtime merges

Why

This is client-side defense in depth for the cursor compatibility issue addressed by vercel/workflow-server#454. Older or partially migrated server behavior can reject a cursor, ignore it, or restart a continuation read at the beginning of the log. The runtime should not replay duplicated events or loop indefinitely when that happens.

The server still needs to preserve legacy cursor compatibility for clients that have already shipped. These guards make newly released clients more tolerant of rejected or overlapping reads and more explicit about malformed pagination responses.

Validation

  • pnpm --filter '@workflow/core...' build
  • pnpm --filter @workflow/core typecheck
  • pnpm --filter @workflow/core test (1080 tests passed)
  • pnpm exec biome check packages/core/src/runtime/helpers.ts packages/core/src/runtime/helpers.test.ts .changeset/calm-events-guard.md
  • git diff --check

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 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 31, 2026 8:23am
example-nextjs-workflow-webpack Ready Ready Preview, Comment May 31, 2026 8:23am
example-workflow Ready Ready Preview, Comment May 31, 2026 8:23am
workbench-astro-workflow Ready Ready Preview, Comment May 31, 2026 8:23am
workbench-express-workflow Ready Ready Preview, Comment May 31, 2026 8:23am
workbench-fastify-workflow Ready Ready Preview, Comment May 31, 2026 8:23am
workbench-hono-workflow Ready Ready Preview, Comment May 31, 2026 8:23am
workbench-nitro-workflow Ready Ready Preview, Comment May 31, 2026 8:23am
workbench-nuxt-workflow Ready Ready Preview, Comment May 31, 2026 8:23am
workbench-sveltekit-workflow Ready Ready Preview, Comment May 31, 2026 8:23am
workbench-tanstack-start-workflow Ready Ready Preview, Comment May 31, 2026 8:23am
workbench-vite-workflow Ready Ready Preview, Comment May 31, 2026 8:23am
workflow-docs Ready Ready Preview, Comment, Open in v0 May 31, 2026 8:23am
workflow-swc-playground Ready Ready Preview, Comment May 31, 2026 8:23am
workflow-tarballs Ready Ready Preview, Comment May 31, 2026 8:23am
workflow-web Ready Ready Preview, Comment May 31, 2026 8:23am

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 30, 2026

🦋 Changeset detected

Latest commit: 7ad78bb

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

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

github-actions Bot commented May 30, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
❌ ▲ Vercel Production 1265 1 219 1485
✅ 💻 Local Development 1671 0 219 1890
✅ 📦 Local Production 1671 0 219 1890
✅ 🐘 Local Postgres 1671 0 219 1890
✅ 🪟 Windows 135 0 0 135
✅ 📋 Other 769 0 176 945
Total 7182 1 1052 8235

❌ Failed Tests

▲ Vercel Production (1 failed)

vite (1 failed):

  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()

Details by Category

❌ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 109 0 26
✅ example 109 0 26
✅ express 109 0 26
✅ fastify 109 0 26
✅ hono 109 0 26
✅ nextjs-turbopack 133 0 2
✅ nextjs-webpack 133 0 2
✅ nitro 109 0 26
✅ nuxt 109 0 26
✅ sveltekit 128 0 7
❌ vite 108 1 26
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 110 0 25
✅ express-stable 110 0 25
✅ fastify-stable 110 0 25
✅ hono-stable 110 0 25
✅ nextjs-turbopack-canary 116 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 135 0 0
✅ nextjs-webpack-canary 116 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 135 0 0
✅ nitro-stable 110 0 25
✅ nuxt-stable 110 0 25
✅ sveltekit-stable 129 0 6
✅ vite-stable 110 0 25
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 110 0 25
✅ express-stable 110 0 25
✅ fastify-stable 110 0 25
✅ hono-stable 110 0 25
✅ nextjs-turbopack-canary 116 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 135 0 0
✅ nextjs-webpack-canary 116 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 135 0 0
✅ nitro-stable 110 0 25
✅ nuxt-stable 110 0 25
✅ sveltekit-stable 129 0 6
✅ vite-stable 110 0 25
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 110 0 25
✅ express-stable 110 0 25
✅ fastify-stable 110 0 25
✅ hono-stable 110 0 25
✅ nextjs-turbopack-canary 116 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 135 0 0
✅ nextjs-webpack-canary 116 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 135 0 0
✅ nitro-stable 110 0 25
✅ nuxt-stable 110 0 25
✅ sveltekit-stable 129 0 6
✅ vite-stable 110 0 25
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 135 0 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 110 0 25
✅ e2e-local-dev-tanstack-start- 110 0 25
✅ e2e-local-postgres-nest-stable 110 0 25
✅ e2e-local-postgres-tanstack-start- 110 0 25
✅ e2e-local-prod-nest-stable 110 0 25
✅ e2e-local-prod-tanstack-start- 110 0 25
✅ e2e-vercel-prod-tanstack-start 109 0 26

📋 View full workflow run


Some E2E test jobs failed:

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

Check the workflow run for details.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 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 🥇 Express 0.031s (-30.0% 🟢) 1.005s (~) 0.974s 10 1.00x
💻 Local Nitro 0.040s (-8.1% 🟢) 1.005s (~) 0.966s 10 1.28x
🐘 Postgres Nitro 0.059s (-37.5% 🟢) 1.012s (-2.9%) 0.953s 10 1.92x
💻 Local Next.js (Turbopack) 0.061s 1.005s 0.945s 10 1.96x
🐘 Postgres Express 0.065s (+12.4% 🔺) 1.011s (~) 0.946s 10 2.10x
🐘 Postgres Next.js (Turbopack) 0.069s 1.012s 0.943s 10 2.23x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 0.308s (-24.9% 🟢) 2.333s (-7.0% 🟢) 2.025s 10 1.00x
▲ Vercel Express 0.352s (+49.4% 🔺) 2.208s (+3.4%) 1.857s 10 1.14x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro | Express

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 1.075s (-4.5%) 2.005s (~) 0.930s 10 1.00x
💻 Local Nitro 1.094s (-3.3%) 2.007s (~) 0.913s 10 1.02x
🐘 Postgres Express 1.107s (-3.4%) 2.010s (~) 0.903s 10 1.03x
🐘 Postgres Nitro 1.108s (-2.8%) 2.009s (~) 0.901s 10 1.03x
💻 Local Next.js (Turbopack) 1.125s 2.005s 0.880s 10 1.05x
🐘 Postgres Next.js (Turbopack) 1.137s 2.009s 0.872s 10 1.06x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.680s (-56.8% 🟢) 4.100s (-30.6% 🟢) 2.420s 10 1.00x
▲ Vercel Express 1.721s (-8.2% 🟢) 4.467s (+17.3% 🔺) 2.745s 10 1.02x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro | Express

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 10.440s (-4.4%) 11.019s (~) 0.580s 3 1.00x
💻 Local Nitro 10.507s (-4.0%) 11.023s (~) 0.516s 3 1.01x
🐘 Postgres Express 10.536s (-3.9%) 11.017s (~) 0.481s 3 1.01x
🐘 Postgres Nitro 10.558s (-2.9%) 11.017s (~) 0.460s 3 1.01x
💻 Local Next.js (Turbopack) 10.756s 11.022s 0.266s 3 1.03x
🐘 Postgres Next.js (Turbopack) 10.845s 11.020s 0.175s 3 1.04x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 13.009s (-45.2% 🟢) 15.566s (-38.0% 🟢) 2.556s 2 1.00x
▲ Vercel Express 13.418s (-21.0% 🟢) 15.810s (-21.0% 🟢) 2.393s 2 1.03x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro | Express

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 13.441s (-10.2% 🟢) 14.025s (-6.7% 🟢) 0.584s 5 1.00x
🐘 Postgres Nitro 13.734s (-5.9% 🟢) 14.018s (-6.7% 🟢) 0.284s 5 1.02x
💻 Local Nitro 13.762s (-8.6% 🟢) 14.026s (-12.5% 🟢) 0.264s 5 1.02x
🐘 Postgres Express 13.821s (-5.2% 🟢) 14.017s (-6.7% 🟢) 0.196s 5 1.03x
💻 Local Next.js (Turbopack) 14.356s 15.030s 0.674s 4 1.07x
🐘 Postgres Next.js (Turbopack) 14.416s 15.015s 0.599s 4 1.07x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 21.094s (-58.1% 🟢) 23.434s (-55.4% 🟢) 2.340s 3 1.00x
▲ Vercel Nitro 21.768s (-66.2% 🟢) 23.830s (-64.2% 🟢) 2.062s 3 1.03x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Express | Nitro

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 11.959s (-28.0% 🟢) 12.271s (-27.9% 🟢) 0.312s 8 1.00x
🐘 Postgres Nitro 12.346s (-11.6% 🟢) 13.016s (-9.0% 🟢) 0.669s 7 1.03x
💻 Local Nitro 12.424s (-26.0% 🟢) 13.023s (-23.5% 🟢) 0.599s 7 1.04x
🐘 Postgres Express 12.471s (-11.0% 🟢) 13.018s (-10.8% 🟢) 0.548s 7 1.04x
💻 Local Next.js (Turbopack) 13.494s 14.026s 0.532s 7 1.13x
🐘 Postgres Next.js (Turbopack) 13.719s 14.021s 0.303s 7 1.15x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 27.813s (-77.1% 🟢) 32.254s (-73.9% 🟢) 4.442s 3 1.00x
▲ Vercel Nitro 28.238s (-93.3% 🟢) 32.579s (-92.3% 🟢) 4.341s 3 1.02x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Express | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 1.147s (-22.9% 🟢) 2.005s (~) 0.858s 15 1.00x
🐘 Postgres Nitro 1.178s (-7.6% 🟢) 2.007s (~) 0.830s 15 1.03x
🐘 Postgres Express 1.193s (-5.3% 🟢) 2.007s (~) 0.814s 15 1.04x
💻 Local Nitro 1.243s (-23.8% 🟢) 2.006s (-3.3%) 0.763s 15 1.08x
🐘 Postgres Next.js (Turbopack) 1.251s 2.007s 0.756s 15 1.09x
💻 Local Next.js (Turbopack) 1.304s 2.005s 0.702s 15 1.14x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.490s (-11.7% 🟢) 3.972s (-8.1% 🟢) 1.482s 8 1.00x
▲ Vercel Express 2.702s (-5.5% 🟢) 4.494s (-2.8%) 1.793s 8 1.09x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro | Express

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.242s (-47.2% 🟢) 2.007s (-33.3% 🟢) 0.765s 15 1.00x
🐘 Postgres Express 1.265s (-46.4% 🟢) 2.007s (-33.3% 🟢) 0.743s 15 1.02x
🐘 Postgres Next.js (Turbopack) 1.369s 2.007s 0.638s 15 1.10x
💻 Local Express 1.566s (-47.0% 🟢) 2.005s (-41.9% 🟢) 0.438s 15 1.26x
💻 Local Next.js (Turbopack) 1.792s 2.073s 0.281s 15 1.44x
💻 Local Nitro 1.910s (-39.2% 🟢) 2.314s (-40.4% 🟢) 0.404s 13 1.54x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.134s (-13.4% 🟢) 5.008s (-2.0%) 1.874s 6 1.00x
▲ Vercel Nitro 3.439s (-15.1% 🟢) 5.180s (-12.5% 🟢) 1.741s 6 1.10x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Express | Nitro

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.392s (-60.1% 🟢) 2.007s (-50.0% 🟢) 0.614s 15 1.00x
🐘 Postgres Nitro 1.399s (-59.8% 🟢) 2.008s (-49.9% 🟢) 0.608s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.782s 2.225s 0.443s 14 1.28x
💻 Local Express 3.767s (-54.8% 🟢) 4.296s (-52.4% 🟢) 0.529s 7 2.71x
💻 Local Next.js (Turbopack) 4.746s 5.347s 0.600s 6 3.41x
💻 Local Nitro 5.282s (-36.7% 🟢) 6.013s (-33.3% 🟢) 0.731s 5 3.79x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 5.026s (+18.5% 🔺) 7.625s (+24.4% 🔺) 2.599s 5 1.00x
▲ Vercel Nitro 5.636s (+59.9% 🔺) 8.489s (+53.4% 🔺) 2.853s 4 1.12x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Express | Nitro

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.181s (-6.0% 🟢) 2.007s (~) 0.826s 15 1.00x
🐘 Postgres Express 1.212s (-3.6%) 2.008s (~) 0.796s 15 1.03x
🐘 Postgres Next.js (Turbopack) 1.236s 2.008s 0.772s 15 1.05x
💻 Local Express 1.379s (-27.2% 🟢) 2.005s (-15.2% 🟢) 0.626s 15 1.17x
💻 Local Next.js (Turbopack) 1.384s 2.005s 0.621s 15 1.17x
💻 Local Nitro 1.490s (-20.2% 🟢) 2.006s (-14.3% 🟢) 0.517s 15 1.26x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.477s (-4.0%) 3.880s (-10.8% 🟢) 1.402s 8 1.00x
▲ Vercel Nitro 2.490s (+1.2%) 3.982s (-4.5%) 1.493s 8 1.00x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Express | Nitro

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.248s (-46.7% 🟢) 2.008s (-33.3% 🟢) 0.761s 15 1.00x
🐘 Postgres Express 1.290s (-44.9% 🟢) 2.010s (-33.3% 🟢) 0.720s 15 1.03x
🐘 Postgres Next.js (Turbopack) 1.376s 2.008s 0.632s 15 1.10x
💻 Local Express 1.676s (-46.5% 🟢) 2.072s (-44.9% 🟢) 0.396s 15 1.34x
💻 Local Next.js (Turbopack) 2.131s 2.736s 0.605s 11 1.71x
💻 Local Nitro 2.205s (-28.1% 🟢) 2.827s (-27.3% 🟢) 0.622s 11 1.77x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.221s (~) 6.728s (+32.5% 🔺) 3.507s 5 1.00x
▲ Vercel Express 3.582s (+12.2% 🔺) 5.489s (+14.5% 🔺) 1.906s 6 1.11x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro | Express

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.387s (-60.1% 🟢) 2.009s (-49.9% 🟢) 0.621s 15 1.00x
🐘 Postgres Express 1.392s (-60.2% 🟢) 2.008s (-49.9% 🟢) 0.616s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.685s 2.077s 0.392s 15 1.21x
💻 Local Express 3.897s (-55.7% 🟢) 4.438s (-52.1% 🟢) 0.540s 7 2.81x
💻 Local Next.js (Turbopack) 5.212s 5.513s 0.300s 6 3.76x
💻 Local Nitro 6.472s (-29.2% 🟢) 7.214s (-28.0% 🟢) 0.742s 5 4.66x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 4.582s (-10.0% 🟢) 6.982s (+2.4%) 2.400s 5 1.00x
▲ Vercel Express 4.891s (-23.8% 🟢) 8.929s (+9.2% 🔺) 4.037s 4 1.07x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro | Express

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 0.511s (-48.1% 🟢) 1.003s (-6.7% 🟢) 0.492s 60 1.00x
🐘 Postgres Nitro 0.556s (-32.2% 🟢) 1.024s (+1.7%) 0.468s 59 1.09x
💻 Local Nitro 0.584s (-40.5% 🟢) 1.005s (-8.1% 🟢) 0.421s 60 1.14x
🐘 Postgres Express 0.610s (-27.3% 🟢) 1.041s (+1.7%) 0.431s 58 1.19x
🐘 Postgres Next.js (Turbopack) 0.800s 1.006s 0.206s 60 1.56x
💻 Local Next.js (Turbopack) 0.848s 1.004s 0.156s 60 1.66x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 5.225s (-72.5% 🟢) 7.527s (-64.7% 🟢) 2.302s 8 1.00x
▲ Vercel Nitro 5.613s (-74.5% 🟢) 8.787s (-63.4% 🟢) 3.174s 7 1.07x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Express | Nitro

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 1.202s (-60.1% 🟢) 2.005s (-44.1% 🟢) 0.803s 45 1.00x
🐘 Postgres Nitro 1.288s (-33.2% 🟢) 2.008s (-4.4%) 0.720s 45 1.07x
🐘 Postgres Express 1.375s (-30.4% 🟢) 2.030s (-10.1% 🟢) 0.655s 45 1.14x
💻 Local Nitro 1.473s (-51.5% 🟢) 2.006s (-46.6% 🟢) 0.533s 45 1.23x
🐘 Postgres Next.js (Turbopack) 1.947s 2.152s 0.204s 42 1.62x
💻 Local Next.js (Turbopack) 2.082s 2.882s 0.800s 32 1.73x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 12.279s (-68.9% 🟢) 14.487s (-64.9% 🟢) 2.208s 7 1.00x
▲ Vercel Express 12.542s (-63.7% 🟢) 15.183s (-58.8% 🟢) 2.641s 6 1.02x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro | Express

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 2.649s (-35.5% 🟢) 3.085s (-33.0% 🟢) 0.437s 39 1.00x
🐘 Postgres Express 2.769s (-30.6% 🟢) 3.166s (-27.5% 🟢) 0.397s 38 1.05x
💻 Local Express 2.852s (-69.0% 🟢) 3.217s (-67.9% 🟢) 0.366s 38 1.08x
💻 Local Nitro 3.184s (-65.8% 🟢) 4.009s (-60.0% 🟢) 0.825s 30 1.20x
🐘 Postgres Next.js (Turbopack) 3.910s 4.144s 0.234s 30 1.48x
💻 Local Next.js (Turbopack) 4.225s 5.011s 0.785s 24 1.60x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 23.475s (-75.8% 🟢) 26.979s (-72.6% 🟢) 3.504s 5 1.00x
▲ Vercel Express 25.025s (-80.8% 🟢) 28.494s (-78.4% 🟢) 3.469s 5 1.07x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro | Express

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.215s (-24.0% 🟢) 1.006s (~) 0.791s 60 1.00x
🐘 Postgres Nitro 0.215s (-24.1% 🟢) 1.006s (~) 0.791s 60 1.00x
🐘 Postgres Next.js (Turbopack) 0.278s 1.007s 0.729s 60 1.29x
💻 Local Express 0.416s (-25.8% 🟢) 1.020s (+1.6%) 0.604s 59 1.94x
💻 Local Nitro 0.419s (-30.8% 🟢) 1.004s (-1.7%) 0.586s 60 1.95x
💻 Local Next.js (Turbopack) 0.541s 1.004s 0.463s 60 2.52x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.110s (+8.0% 🔺) 4.415s (+21.4% 🔺) 2.304s 14 1.00x
▲ Vercel Nitro 2.304s (+38.7% 🔺) 4.047s (+20.8% 🔺) 1.744s 15 1.09x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Express | Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.330s (-33.6% 🟢) 1.006s (~) 0.677s 90 1.00x
🐘 Postgres Express 0.342s (-32.9% 🟢) 1.007s (~) 0.665s 90 1.04x
🐘 Postgres Next.js (Turbopack) 0.483s 1.006s 0.523s 90 1.46x
💻 Local Express 1.728s (-31.2% 🟢) 2.227s (-26.0% 🟢) 0.498s 41 5.24x
💻 Local Nitro 2.164s (-14.8% 🟢) 2.736s (-9.1% 🟢) 0.572s 33 6.56x
💻 Local Next.js (Turbopack) 2.456s 3.111s 0.655s 30 7.45x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 4.222s (+30.9% 🔺) 6.516s (+35.1% 🔺) 2.294s 15 1.00x
▲ Vercel Express 4.489s (+47.3% 🔺) 6.596s (+37.2% 🔺) 2.107s 14 1.06x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro | Express

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.650s (-17.8% 🟢) 1.006s (~) 0.356s 120 1.00x
🐘 Postgres Express 0.673s (-17.8% 🟢) 1.006s (-1.1%) 0.333s 120 1.03x
🐘 Postgres Next.js (Turbopack) 0.982s 1.630s 0.648s 74 1.51x
💻 Local Express 7.937s (-29.1% 🟢) 8.557s (-28.3% 🟢) 0.620s 15 12.21x
💻 Local Nitro 9.549s (-14.7% 🟢) 10.195s (-12.6% 🟢) 0.646s 12 14.69x
💻 Local Next.js (Turbopack) 10.601s 11.302s 0.702s 11 16.31x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 12.601s (+63.2% 🔺) 14.844s (+57.9% 🔺) 2.244s 9 1.00x
▲ Vercel Express 12.905s (+73.9% 🔺) 15.064s (+63.0% 🔺) 2.160s 9 1.02x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro | Express

Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 1.129s (+467.1% 🔺) 2.004s (+99.5% 🔺) 0.008s (-34.7% 🟢) 2.014s (+97.8% 🔺) 0.885s 10 1.00x
🐘 Postgres Nitro 1.155s (+463.6% 🔺) 2.001s (+100.2% 🔺) 0.001s (-20.0% 🟢) 2.010s (+98.8% 🔺) 0.855s 10 1.02x
💻 Local Nitro 1.160s (+442.8% 🔺) 2.005s (+99.6% 🔺) 0.012s (-3.2%) 2.019s (+98.2% 🔺) 0.859s 10 1.03x
🐘 Postgres Express 1.171s (+471.1% 🔺) 1.998s (+100.1% 🔺) 0.002s (~) 2.011s (+98.8% 🔺) 0.840s 10 1.04x
💻 Local Next.js (Turbopack) 1.198s 2.003s 0.010s 2.017s 0.819s 10 1.06x
🐘 Postgres Next.js (Turbopack) 1.266s 2.002s 0.002s 2.011s 0.745s 10 1.12x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.212s (-42.3% 🟢) 3.747s (-29.0% 🟢) 13.358s (+1700.1% 🔺) 17.538s (+170.5% 🔺) 15.327s 10 1.00x
▲ Vercel Express 2.526s (+0.8%) 3.228s (-21.1% 🟢) 1.656s (+72.4% 🔺) 5.854s (+4.7%) 3.328s 10 1.14x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -

🔍 Observability: Nitro | Express

stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.578s (+88.2% 🔺) 2.010s (+98.6% 🔺) 0.010s (+9.6% 🔺) 2.022s (+81.2% 🔺) 0.444s 30 1.00x
🐘 Postgres Nitro 1.581s (+153.3% 🔺) 2.005s (+99.1% 🔺) 0.004s (-10.5% 🟢) 2.025s (+98.1% 🔺) 0.444s 30 1.00x
🐘 Postgres Express 1.582s (+151.0% 🔺) 2.006s (+99.3% 🔺) 0.004s (+1.8%) 2.025s (+97.9% 🔺) 0.443s 30 1.00x
🐘 Postgres Next.js (Turbopack) 1.743s 2.010s 0.004s 2.026s 0.284s 30 1.10x
💻 Local Next.js (Turbopack) 1.744s 2.006s 0.011s 2.022s 0.279s 30 1.10x
💻 Local Express 1.859s (+145.6% 🔺) 2.007s (+95.0% 🔺) 0.008s (-19.3% 🟢) 2.417s (+132.5% 🔺) 0.558s 25 1.18x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 5.739s (-11.8% 🟢) 7.290s (-9.0% 🟢) 0.201s (-50.8% 🟢) 8.306s (-6.0% 🟢) 2.567s 8 1.00x
▲ Vercel Nitro 6.087s (-79.3% 🟢) 8.642s (-71.9% 🟢) 0.194s (+73.3% 🔺) 9.371s (-70.5% 🟢) 3.284s 7 1.06x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -

🔍 Observability: Express | Nitro

10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.696s (-28.1% 🟢) 1.031s (-17.3% 🟢) 0.000s (+106.9% 🔺) 1.049s (-16.6% 🟢) 0.353s 58 1.00x
🐘 Postgres Express 0.736s (-23.4% 🟢) 1.069s (-16.4% 🟢) 0.000s (-100.0% 🟢) 1.081s (-17.2% 🟢) 0.345s 56 1.06x
🐘 Postgres Next.js (Turbopack) 0.834s 1.092s 0.000s 1.098s 0.264s 55 1.20x
💻 Local Express 1.176s (-4.0%) 1.886s (-6.7% 🟢) 0.000s (+12.5% 🔺) 1.888s (-6.6% 🟢) 0.712s 32 1.69x
💻 Local Nitro 1.370s (+12.1% 🔺) 2.013s (~) 0.000s (+200.0% 🔺) 2.015s (~) 0.645s 30 1.97x
💻 Local Next.js (Turbopack) 1.668s 2.013s 0.000s 2.195s 0.527s 28 2.40x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.389s (+11.1% 🔺) 4.707s (+7.2% 🔺) 0.004s (+5750.0% 🔺) 5.240s (+9.0% 🔺) 1.851s 12 1.00x
▲ Vercel Express 3.482s (-6.9% 🟢) 5.691s (+11.5% 🔺) 0.000s (-100.0% 🟢) 6.214s (+12.4% 🔺) 2.732s 10 1.03x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -

🔍 Observability: Nitro | Express

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.335s (-24.7% 🟢) 2.065s (-5.2% 🟢) 0.000s (+Infinity% 🔺) 2.084s (-5.2% 🟢) 0.749s 29 1.00x
🐘 Postgres Nitro 1.360s (-24.1% 🟢) 2.103s (-1.8%) 0.000s (-3.4%) 2.124s (-2.3%) 0.764s 29 1.02x
🐘 Postgres Next.js (Turbopack) 1.695s 2.225s 0.000s 2.233s 0.538s 27 1.27x
💻 Local Express 2.443s (-29.5% 🟢) 2.975s (-26.3% 🟢) 0.000s (-76.2% 🟢) 2.979s (-26.2% 🟢) 0.536s 21 1.83x
💻 Local Next.js (Turbopack) 3.109s 3.777s 0.001s 3.781s 0.672s 16 2.33x
💻 Local Nitro 3.119s (-7.9% 🟢) 3.891s (-3.5%) 0.001s (+17.2% 🔺) 3.903s (-3.3%) 0.784s 16 2.34x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.077s (+24.0% 🔺) 7.360s (+36.9% 🔺) 0.000s (-18.5% 🟢) 7.768s (+34.1% 🔺) 2.691s 9 1.00x
▲ Vercel Express 5.131s (+11.9% 🔺) 7.584s (+25.9% 🔺) 0.005s (+Infinity% 🔺) 8.157s (+26.3% 🔺) 3.026s 8 1.01x
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -

🔍 Observability: Nitro | Express

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Express 20/21
🐘 Postgres Nitro 16/21
▲ Vercel Nitro 13/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)
  • 🌐 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: success
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

@pranaygp pranaygp marked this pull request as ready for review May 31, 2026 01:51
@pranaygp pranaygp requested a review from a team as a code owner May 31, 2026 01:51
Copilot AI review requested due to automatic review settings May 31, 2026 01:51
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

This PR hardens runtime event-log pagination so clients can tolerate rejected or overlapping cursors without replaying duplicate events or looping indefinitely.

Changes:

  • Adds pagination guard helpers for cursor progress validation, deduplication, and one-time retry after rejected continuation cursors.
  • Adds tests covering overlapping pages, rejected cursors, repeated cursors, and missing cursors with hasMore.
  • Fixes incremental event merges to update the local existingIds set while appending.

Reviewed changes

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

File Description
packages/core/src/runtime/helpers.ts Adds event pagination deduplication, cursor retry, and contract-error guards.
packages/core/src/runtime/helpers.test.ts Adds regression tests for hardened pagination behavior.
packages/core/src/runtime.ts Prevents duplicate IDs during incremental runtime event merges.
.changeset/calm-events-guard.md Records a patch release note for the runtime pagination hardening.

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

Copy link
Copy Markdown
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.

See review on #2179, please address the same nits

@VaguelySerious VaguelySerious merged commit 1ee63b8 into main May 31, 2026
111 of 119 checks passed
@VaguelySerious VaguelySerious deleted the pranaygp/codex/harden-event-pagination branch May 31, 2026 08:48
@github-actions
Copy link
Copy Markdown
Contributor

No backport to stable for 1ee63b8 (AI decision).

The stable branch already contains all of this hardening code: eventPaginationContractError, appendUniqueEvents, assertEventPaginationProgress, shouldRetryWithoutEventCursor, loadedEventIds/requestedCursors, the existingIds.add fix in runtime.ts, and the four new test cases (deduplicates overlapping pages, retries a rejected continuation cursor, fails instead of looping, fails when a response reports more pages without a cursor) are all already present on stable. This PR is forward-porting an already-stable fix to main, so there is nothing to backport.

To override, re-run the Backport to stable workflow manually via workflow_dispatch and paste this commit SHA into the ref input:

1ee63b870afbf9754eb1022b1bb5f02d0ab042f9

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