Skip to content

fix(core): propagate stream cancellation on disconnect#1618

Merged
VaguelySerious merged 1 commit intomainfrom
peter/fix-1349-stream-cancel
Apr 7, 2026
Merged

fix(core): propagate stream cancellation on disconnect#1618
VaguelySerious merged 1 commit intomainfrom
peter/fix-1349-stream-cancel

Conversation

@VaguelySerious
Copy link
Copy Markdown
Member

@VaguelySerious VaguelySerious commented Apr 6, 2026

Summary

  • Adds reader.cancel() in flushablePipe's finally block so the source stream is notified on disconnect, with error reason passed via a local variable to avoid double-cancel
  • Races reader.read() against writer.closed to detect premature sink closure faster
  • Adds cancel(reason) method to WorkflowServerReadableStream so cancellation propagates to the inner reader
  • Adds a new test for cancellation propagation while retaining all existing tests

Based on #1354, with all review feedback addressed.
Closes #1354
Fixes #1349

Test plan

  • All 9 unit tests pass in flushable-stream.test.ts (8 original + 1 new cancellation test)
  • All 117 serialization tests pass
  • Typecheck passes across all 41 packages
  • CI checks pass

🤖 Generated with Claude Code

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 6, 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 Apr 6, 2026 7:44pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Apr 6, 2026 7:44pm
example-workflow Ready Ready Preview, Comment Apr 6, 2026 7:44pm
workbench-astro-workflow Ready Ready Preview, Comment Apr 6, 2026 7:44pm
workbench-express-workflow Ready Ready Preview, Comment Apr 6, 2026 7:44pm
workbench-fastify-workflow Ready Ready Preview, Comment Apr 6, 2026 7:44pm
workbench-hono-workflow Ready Ready Preview, Comment Apr 6, 2026 7:44pm
workbench-nitro-workflow Ready Ready Preview, Comment Apr 6, 2026 7:44pm
workbench-nuxt-workflow Ready Ready Preview, Comment Apr 6, 2026 7:44pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Apr 6, 2026 7:44pm
workbench-vite-workflow Ready Ready Preview, Comment Apr 6, 2026 7:44pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Apr 6, 2026 7:44pm
workflow-swc-playground Ready Ready Preview, Comment Apr 6, 2026 7:44pm

@VaguelySerious VaguelySerious requested a review from a team as a code owner April 6, 2026 19:29
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 6, 2026

🦋 Changeset detected

Latest commit: 43f1952

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 Patch
@workflow/world-testing Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/nuxt Patch
@workflow/ai 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 Apr 6, 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.043s (+4.3%) 1.006s (~) 0.962s 10 1.00x
💻 Local Next.js (Turbopack) 0.049s 1.006s 0.957s 10 1.12x
🌐 Redis Next.js (Turbopack) 0.053s 1.005s 0.951s 10 1.23x
🐘 Postgres Express 0.055s (-9.7% 🟢) 1.009s (~) 0.954s 10 1.27x
🐘 Postgres Next.js (Turbopack) 0.057s 1.010s 0.954s 10 1.30x
🐘 Postgres Nitro 0.060s (-7.7% 🟢) 1.009s (~) 0.949s 10 1.39x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 0.227s (-51.8% 🟢) 1.999s (-27.9% 🟢) 1.771s 10 1.00x
▲ Vercel Next.js (Turbopack) 0.247s (-64.2% 🟢) 2.244s (-22.1% 🟢) 1.997s 10 1.09x
▲ Vercel Nitro 0.266s (-55.7% 🟢) 2.263s (-16.2% 🟢) 1.997s 10 1.17x

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

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Next.js (Turbopack) 1.115s 2.006s 0.891s 10 1.00x
🌐 Redis Next.js (Turbopack) 1.121s 2.006s 0.886s 10 1.01x
🐘 Postgres Express 1.121s (-2.2%) 2.008s (~) 0.887s 10 1.01x
💻 Local Express 1.127s (~) 2.006s (~) 0.879s 10 1.01x
🐘 Postgres Next.js (Turbopack) 1.142s 2.009s 0.867s 10 1.02x
🐘 Postgres Nitro 1.148s (~) 2.015s (~) 0.867s 10 1.03x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.877s (-15.4% 🟢) 3.675s (-8.2% 🟢) 1.797s 10 1.00x
▲ Vercel Next.js (Turbopack) 1.900s (-8.9% 🟢) 3.684s (-2.5%) 1.783s 10 1.01x
▲ Vercel Express 2.043s (-13.1% 🟢) 3.526s (-12.1% 🟢) 1.483s 10 1.09x

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

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 10.704s (-1.6%) 11.020s (~) 0.316s 3 1.00x
💻 Local Next.js (Turbopack) 10.777s 11.023s 0.245s 3 1.01x
🌐 Redis Next.js (Turbopack) 10.812s 11.023s 0.210s 3 1.01x
🐘 Postgres Nitro 10.900s (~) 11.023s (~) 0.123s 3 1.02x
🐘 Postgres Next.js (Turbopack) 10.901s 11.358s 0.457s 3 1.02x
💻 Local Express 10.923s (~) 11.023s (~) 0.100s 3 1.02x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 16.872s (+1.0%) 18.705s (+1.7%) 1.833s 2 1.00x
▲ Vercel Next.js (Turbopack) 17.122s (+2.2%) 18.560s (+1.2%) 1.438s 2 1.01x
▲ Vercel Express 18.029s (+11.7% 🔺) 19.645s (+8.6% 🔺) 1.617s 2 1.07x

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

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 14.059s (-3.7%) 14.820s (-1.3%) 0.761s 5 1.00x
🌐 Redis Next.js (Turbopack) 14.223s 15.029s 0.806s 4 1.01x
🐘 Postgres Nitro 14.491s (~) 15.024s (~) 0.533s 4 1.03x
🐘 Postgres Next.js (Turbopack) 14.499s 15.025s 0.526s 4 1.03x
💻 Local Next.js (Turbopack) 14.627s 15.029s 0.402s 4 1.04x
💻 Local Express 15.015s (+0.6%) 15.278s (+1.7%) 0.263s 4 1.07x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 32.294s (+5.6% 🔺) 34.178s (+6.1% 🔺) 1.884s 2 1.00x
▲ Vercel Next.js (Turbopack) 32.740s (+4.3%) 34.715s (+4.0%) 1.974s 2 1.01x
▲ Vercel Nitro 34.286s (+12.8% 🔺) 35.882s (+8.4% 🔺) 1.596s 2 1.06x

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

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 13.058s (-6.3% 🟢) 13.879s (-2.0%) 0.821s 7 1.00x
🌐 Redis Next.js (Turbopack) 13.572s 14.168s 0.596s 7 1.04x
🐘 Postgres Nitro 14.006s (~) 14.452s (~) 0.446s 7 1.07x
🐘 Postgres Next.js (Turbopack) 14.024s 14.305s 0.281s 7 1.07x
💻 Local Next.js (Turbopack) 16.153s 17.031s 0.878s 6 1.24x
💻 Local Express 16.815s (+1.3%) 17.030s (~) 0.215s 6 1.29x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 52.659s (+10.4% 🔺) 54.782s (+11.0% 🔺) 2.123s 2 1.00x
▲ Vercel Express 56.496s (+15.2% 🔺) 58.265s (+14.0% 🔺) 1.768s 2 1.07x
▲ Vercel Next.js (Turbopack) 61.180s (+21.8% 🔺) 62.910s (+21.0% 🔺) 1.730s 2 1.16x

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

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.211s (-4.3%) 2.008s (~) 0.797s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.223s 2.009s 0.786s 15 1.01x
🐘 Postgres Nitro 1.261s (~) 2.010s (~) 0.749s 15 1.04x
🌐 Redis Next.js (Turbopack) 1.370s 2.006s 0.636s 15 1.13x
💻 Local Express 1.534s (~) 2.006s (~) 0.473s 15 1.27x
💻 Local Next.js (Turbopack) 1.570s 2.073s 0.503s 15 1.30x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.536s (-20.7% 🟢) 4.183s (-13.1% 🟢) 1.647s 8 1.00x
▲ Vercel Express 2.699s (+14.9% 🔺) 4.498s (+7.6% 🔺) 1.800s 7 1.06x
▲ Vercel Next.js (Turbopack) 2.867s (+8.8% 🔺) 4.355s (-3.0%) 1.487s 7 1.13x

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

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.285s (-2.1%) 3.009s (~) 0.724s 10 1.00x
🐘 Postgres Nitro 2.335s (~) 3.010s (~) 0.675s 10 1.02x
🐘 Postgres Next.js (Turbopack) 2.390s 3.009s 0.619s 10 1.05x
🌐 Redis Next.js (Turbopack) 2.556s 3.008s 0.451s 10 1.12x
💻 Local Next.js (Turbopack) 2.868s 3.208s 0.341s 10 1.26x
💻 Local Express 3.062s (+1.4%) 3.759s (+2.3%) 0.697s 8 1.34x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.362s (-20.6% 🟢) 4.035s (-14.0% 🟢) 1.673s 8 1.00x
▲ Vercel Express 2.533s (-18.1% 🟢) 4.231s (-3.1%) 1.698s 8 1.07x
▲ Vercel Next.js (Turbopack) 2.895s (+8.5% 🔺) 4.563s (+5.2% 🔺) 1.668s 7 1.23x

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

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 3.407s (-1.9%) 4.010s (~) 0.602s 8 1.00x
🐘 Postgres Nitro 3.466s (+0.6%) 4.014s (~) 0.548s 8 1.02x
🐘 Postgres Next.js (Turbopack) 3.644s 4.012s 0.368s 8 1.07x
🌐 Redis Next.js (Turbopack) 4.102s 4.725s 0.623s 7 1.20x
💻 Local Next.js (Turbopack) 7.931s 8.519s 0.588s 4 2.33x
💻 Local Express 8.639s (+7.4% 🔺) 9.021s (+5.9% 🔺) 0.382s 4 2.54x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.974s (-10.6% 🟢) 4.704s (-4.8%) 1.730s 7 1.00x
▲ Vercel Express 2.995s (+0.8%) 4.631s (+5.2% 🔺) 1.636s 7 1.01x
▲ Vercel Next.js (Turbopack) 3.234s (-1.7%) 5.173s (+7.4% 🔺) 1.939s 6 1.09x

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

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.205s (-3.8%) 2.009s (~) 0.804s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.220s 2.009s 0.789s 15 1.01x
🐘 Postgres Nitro 1.248s (-1.1%) 2.008s (~) 0.760s 15 1.04x
🌐 Redis Next.js (Turbopack) 1.290s 2.006s 0.716s 15 1.07x
💻 Local Express 1.568s (+2.1%) 2.007s (~) 0.439s 15 1.30x
💻 Local Next.js (Turbopack) 1.899s 2.391s 0.492s 13 1.58x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 2.094s (-26.4% 🟢) 3.788s (-19.0% 🟢) 1.694s 8 1.00x
▲ Vercel Express 2.259s (-4.2%) 3.848s (-10.4% 🟢) 1.590s 8 1.08x
▲ Vercel Nitro 2.354s (+11.0% 🔺) 4.038s (~) 1.684s 8 1.12x

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

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.308s (-1.1%) 3.010s (~) 0.701s 10 1.00x
🐘 Postgres Nitro 2.318s (-0.8%) 3.010s (~) 0.691s 10 1.00x
🐘 Postgres Next.js (Turbopack) 2.386s 3.008s 0.623s 10 1.03x
🌐 Redis Next.js (Turbopack) 2.548s 3.008s 0.460s 10 1.10x
💻 Local Next.js (Turbopack) 3.045s 3.885s 0.840s 8 1.32x
💻 Local Express 3.120s (+0.9%) 3.886s (~) 0.766s 8 1.35x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.276s (-17.0% 🟢) 3.953s (-3.9%) 1.678s 8 1.00x
▲ Vercel Next.js (Turbopack) 2.715s (+8.9% 🔺) 4.375s (+10.4% 🔺) 1.661s 8 1.19x
▲ Vercel Nitro 2.761s (-13.3% 🟢) 4.431s (-9.4% 🟢) 1.669s 7 1.21x

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

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 3.421s (-1.8%) 4.009s (~) 0.588s 8 1.00x
🐘 Postgres Nitro 3.477s (~) 4.011s (~) 0.534s 8 1.02x
🐘 Postgres Next.js (Turbopack) 3.665s 4.014s 0.349s 8 1.07x
🌐 Redis Next.js (Turbopack) 4.151s 5.011s 0.860s 6 1.21x
💻 Local Next.js (Turbopack) 8.357s 9.021s 0.664s 4 2.44x
💻 Local Express 9.097s (+5.4% 🔺) 9.276s (+2.8%) 0.178s 4 2.66x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.600s (-17.2% 🟢) 4.083s (-13.5% 🟢) 1.484s 8 1.00x
▲ Vercel Express 2.808s (-6.9% 🟢) 4.153s (-15.4% 🟢) 1.345s 8 1.08x
▲ Vercel Next.js (Turbopack) 3.650s (+3.5%) 5.634s (+3.0%) 1.984s 6 1.40x

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

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.641s (-23.3% 🟢) 1.006s (~) 0.365s 60 1.00x
🌐 Redis Next.js (Turbopack) 0.686s 1.004s 0.318s 60 1.07x
🐘 Postgres Next.js (Turbopack) 0.773s 1.006s 0.233s 60 1.21x
🐘 Postgres Nitro 0.815s (-1.9%) 1.006s (~) 0.190s 60 1.27x
💻 Local Next.js (Turbopack) 0.856s 1.021s 0.166s 59 1.33x
💻 Local Express 0.998s (+1.6%) 1.401s (+23.2% 🔺) 0.403s 43 1.56x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 9.100s (+5.4% 🔺) 10.996s (+9.1% 🔺) 1.896s 6 1.00x
▲ Vercel Nitro 9.172s (+4.8%) 11.170s (+5.3% 🔺) 1.998s 6 1.01x
▲ Vercel Express 9.772s (+16.5% 🔺) 11.782s (+20.7% 🔺) 2.010s 6 1.07x

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

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.537s (-23.1% 🟢) 2.007s (-20.9% 🟢) 0.470s 45 1.00x
🌐 Redis Next.js (Turbopack) 1.623s 2.006s 0.382s 45 1.06x
🐘 Postgres Next.js (Turbopack) 1.888s 2.100s 0.212s 43 1.23x
🐘 Postgres Nitro 1.968s (~) 2.283s (+1.1%) 0.315s 40 1.28x
💻 Local Next.js (Turbopack) 2.673s 3.008s 0.334s 30 1.74x
💻 Local Express 3.089s (+2.3%) 4.010s (+11.1% 🔺) 0.921s 23 2.01x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 28.105s (+10.1% 🔺) 29.962s (+7.9% 🔺) 1.856s 4 1.00x
▲ Vercel Express 28.765s (+10.3% 🔺) 30.276s (+8.5% 🔺) 1.510s 3 1.02x
▲ Vercel Next.js (Turbopack) 32.579s (+18.5% 🔺) 34.647s (+18.2% 🔺) 2.068s 3 1.16x

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

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 3.095s (-22.7% 🟢) 3.823s (-17.4% 🟢) 0.728s 32 1.00x
🌐 Redis Next.js (Turbopack) 3.308s 4.008s 0.700s 30 1.07x
🐘 Postgres Next.js (Turbopack) 3.850s 4.076s 0.225s 30 1.24x
🐘 Postgres Nitro 3.978s (~) 4.295s (~) 0.317s 28 1.29x
💻 Local Next.js (Turbopack) 8.580s 9.017s 0.437s 14 2.77x
💻 Local Express 9.461s (+3.5%) 10.019s (+2.4%) 0.558s 12 3.06x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 79.438s (+9.7% 🔺) 81.399s (+9.1% 🔺) 1.962s 2 1.00x
▲ Vercel Next.js (Turbopack) 85.996s (+18.8% 🔺) 87.579s (+17.1% 🔺) 1.583s 2 1.08x
▲ Vercel Express 88.634s (+21.5% 🔺) 90.378s (+20.7% 🔺) 1.744s 2 1.12x

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

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.232s (-17.0% 🟢) 1.007s (~) 0.774s 60 1.00x
🐘 Postgres Next.js (Turbopack) 0.245s 1.007s 0.762s 60 1.05x
🐘 Postgres Nitro 0.287s (+4.3%) 1.007s (~) 0.720s 60 1.24x
🌐 Redis Next.js (Turbopack) 0.292s 1.004s 0.712s 60 1.26x
💻 Local Express 0.567s (~) 1.005s (~) 0.437s 60 2.44x
💻 Local Next.js (Turbopack) 0.572s 1.021s 0.449s 59 2.46x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.522s (-18.4% 🟢) 3.015s (-13.2% 🟢) 1.493s 20 1.00x
▲ Vercel Express 1.755s (-2.8%) 3.564s (+5.4% 🔺) 1.808s 17 1.15x
▲ Vercel Next.js (Turbopack) 1.802s (+3.0%) 3.843s (+15.4% 🔺) 2.041s 17 1.18x

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

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.373s (-24.5% 🟢) 1.006s (~) 0.633s 90 1.00x
🐘 Postgres Next.js (Turbopack) 0.467s 1.006s 0.539s 90 1.25x
🐘 Postgres Nitro 0.495s (+1.8%) 1.006s (~) 0.511s 90 1.33x
🌐 Redis Next.js (Turbopack) 1.150s 2.006s 0.855s 45 3.08x
💻 Local Next.js (Turbopack) 2.496s 3.009s 0.513s 30 6.69x
💻 Local Express 2.551s (-0.5%) 3.008s (~) 0.458s 30 6.84x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.950s (~) 4.873s (+0.9%) 1.923s 19 1.00x
▲ Vercel Express 2.971s (-1.6%) 4.590s (-2.4%) 1.619s 20 1.01x
▲ Vercel Next.js (Turbopack) 3.711s (+2.3%) 5.381s (~) 1.670s 17 1.26x

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

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.586s (-25.1% 🟢) 1.006s (~) 0.421s 120 1.00x
🐘 Postgres Next.js (Turbopack) 0.768s 1.006s 0.239s 120 1.31x
🐘 Postgres Nitro 0.781s (+2.3%) 1.007s (~) 0.226s 120 1.33x
🌐 Redis Next.js (Turbopack) 2.674s 3.007s 0.333s 40 4.56x
💻 Local Next.js (Turbopack) 10.732s 11.296s 0.564s 11 18.32x
💻 Local Express 11.385s (+2.0%) 11.938s (+1.6%) 0.553s 11 19.43x
💻 Local Nitro ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 7.579s (+13.1% 🔺) 9.291s (+11.6% 🔺) 1.712s 13 1.00x
▲ Vercel Nitro 8.633s (+17.9% 🔺) 10.208s (+7.8% 🔺) 1.575s 12 1.14x
▲ Vercel Next.js (Turbopack) 80.621s (+995.3% 🔺) 82.863s (+794.7% 🔺) 2.242s 3 10.64x

🔍 Observability: Express | 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
🐘 Postgres 🥇 Express 0.157s (-24.4% 🟢) 0.999s (~) 0.001s (-20.0% 🟢) 1.009s (~) 0.852s 10 1.00x
💻 Local Next.js (Turbopack) 0.171s 1.003s 0.012s 1.018s 0.847s 10 1.09x
🌐 Redis Next.js (Turbopack) 0.176s 1.001s 0.002s 1.008s 0.832s 10 1.12x
🐘 Postgres Next.js (Turbopack) 0.189s 1.000s 0.001s 1.009s 0.820s 10 1.20x
💻 Local Express 0.211s (+5.0% 🔺) 1.004s (~) 0.013s (+11.4% 🔺) 1.019s (~) 0.808s 10 1.35x
🐘 Postgres Nitro 0.212s (-10.1% 🟢) 0.992s (~) 0.001s (-77.8% 🟢) 1.009s (-0.6%) 0.797s 10 1.35x
💻 Local Nitro ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.501s (-1.7%) 3.091s (+22.9% 🔺) 0.355s (-46.9% 🟢) 3.955s (+3.7%) 2.454s 10 1.00x
▲ Vercel Express 1.772s (-6.1% 🟢) 3.348s (+16.5% 🔺) 0.471s (+22.6% 🔺) 4.302s (+8.6% 🔺) 2.530s 10 1.18x
▲ Vercel Next.js (Turbopack) 1.925s (+13.2% 🔺) 3.415s (+29.7% 🔺) 0.478s (-31.7% 🟢) 4.378s (+9.4% 🔺) 2.453s 10 1.28x

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

stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 0.476s 1.002s 0.003s 1.011s 0.535s 60 1.00x
🐘 Postgres Express 0.534s (-11.4% 🟢) 1.005s (~) 0.004s (-3.8%) 1.022s (~) 0.488s 59 1.12x
🐘 Postgres Next.js (Turbopack) 0.601s 1.009s 0.004s 1.021s 0.420s 59 1.26x
🐘 Postgres Nitro 0.636s (+6.5% 🔺) 1.003s (~) 0.003s (-14.8% 🟢) 1.022s (~) 0.387s 59 1.33x
💻 Local Next.js (Turbopack) 0.650s 1.012s 0.009s 1.024s 0.374s 59 1.37x
💻 Local Express 0.743s (+3.1%) 1.012s (~) 0.010s (+12.0% 🔺) 1.024s (~) 0.281s 59 1.56x
💻 Local Nitro ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 4.062s (-92.3% 🟢) 5.293s (-90.3% 🟢) 0.273s (+2.2%) 6.035s (-89.1% 🟢) 1.973s 11 1.00x
▲ Vercel Express 4.101s (-2.2%) 5.612s (+4.1%) 0.518s (+204.0% 🔺) 6.576s (+4.6%) 2.475s 10 1.01x
▲ Vercel Nitro 4.137s (-6.3% 🟢) 5.801s (-0.5%) 0.255s (+46.1% 🔺) 6.474s (-3.7%) 2.337s 10 1.02x

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

10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🌐 Redis 🥇 Next.js (Turbopack) 0.896s 1.017s 0.000s 1.021s 0.126s 59 1.00x
🐘 Postgres Next.js (Turbopack) 0.918s 1.152s 0.000s 1.158s 0.241s 53 1.02x
🐘 Postgres Express 0.949s (-1.9%) 1.152s (-9.1% 🟢) 0.000s (+Infinity% 🔺) 1.170s (-9.0% 🟢) 0.221s 52 1.06x
🐘 Postgres Nitro 0.983s (+3.0%) 1.360s (+23.3% 🔺) 0.000s (-58.3% 🟢) 1.372s (+22.8% 🔺) 0.390s 44 1.10x
💻 Local Express 1.245s (-0.8%) 2.022s (~) 0.000s (+55.6% 🔺) 2.024s (~) 0.779s 30 1.39x
💻 Local Next.js (Turbopack) 1.265s 2.021s 0.000s 2.024s 0.759s 30 1.41x
💻 Local Nitro ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.104s (+11.2% 🔺) 4.278s (+14.6% 🔺) 0.000s (+Infinity% 🔺) 4.689s (+8.9% 🔺) 1.585s 13 1.00x
▲ Vercel Nitro 3.285s (+17.8% 🔺) 4.710s (+33.7% 🔺) 0.000s (-100.0% 🟢) 5.185s (+22.4% 🔺) 1.901s 12 1.06x
▲ Vercel Next.js (Turbopack) 3.289s (+12.3% 🔺) 4.634s (+19.8% 🔺) 0.000s (-41.7% 🟢) 5.083s (+10.6% 🔺) 1.794s 12 1.06x

🔍 Observability: Express | 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
🌐 Redis 🥇 Next.js (Turbopack) 1.652s 2.033s 0.000s 2.039s 0.387s 30 1.00x
🐘 Postgres Express 1.705s (-1.6%) 2.070s (-1.4%) 0.000s (-100.0% 🟢) 2.078s (-1.7%) 0.373s 29 1.03x
🐘 Postgres Nitro 1.721s (-9.4% 🟢) 2.100s (-10.4% 🟢) 0.000s (-100.0% 🟢) 2.112s (-10.4% 🟢) 0.391s 29 1.04x
🐘 Postgres Next.js (Turbopack) 1.750s 2.106s 0.000s 2.119s 0.369s 29 1.06x
💻 Local Express 3.605s (~) 4.034s (-1.6%) 0.001s (-20.0% 🟢) 4.036s (-1.6%) 0.432s 15 2.18x
💻 Local Next.js (Turbopack) 3.614s 4.100s 0.000s 4.103s 0.489s 15 2.19x
💻 Local Nitro ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.547s (-20.6% 🟢) 4.856s (-13.0% 🟢) 0.000s (-100.0% 🟢) 5.290s (-14.8% 🟢) 1.743s 12 1.00x
▲ Vercel Express 3.757s (-24.1% 🟢) 5.186s (-18.2% 🟢) 0.008s (+675.0% 🔺) 5.602s (-19.1% 🟢) 1.845s 12 1.06x
▲ Vercel Next.js (Turbopack) 3.982s (-20.4% 🟢) 5.344s (-10.7% 🟢) 0.000s (+63.6% 🔺) 5.827s (-13.7% 🟢) 1.845s 11 1.12x

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

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Next.js (Turbopack) 15/21
🐘 Postgres Express 20/21
▲ Vercel Nitro 13/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 879 0 67 946
✅ 💻 Local Development 854 0 178 1032
✅ 📦 Local Production 854 0 178 1032
✅ 🐘 Local Postgres 854 0 178 1032
✅ 🪟 Windows 78 0 8 86
❌ 🌍 Community Worlds 134 64 24 222
✅ 📋 Other 216 0 42 258
Total 3869 64 675 4608

❌ Failed Tests

🌍 Community Worlds (64 failed)

mongodb (4 failed):

  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KNJ55WAEEQC6QZ3XE7B0WCJY
  • webhookWorkflow | wrun_01KNJ5643NY1W8SE6SK8KF7AFH
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KNJ5DSM50HN5QZRD1FCC7VBW
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_01KNJ5MACCT496N78HZYYBGMSE

redis (3 failed):

  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KNJ55WAEEQC6QZ3XE7B0WCJY
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KNJ5DSM50HN5QZRD1FCC7VBW
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_01KNJ5MACCT496N78HZYYBGMSE

turso (57 failed):

  • addTenWorkflow | wrun_01KNJ54PC315EBAM2BBRRENMVW
  • addTenWorkflow | wrun_01KNJ54PC315EBAM2BBRRENMVW
  • wellKnownAgentWorkflow (.well-known/agent) | wrun_01KNJ55WHYRZW30801TV7JKD9Z
  • should work with react rendering in step
  • promiseAllWorkflow | wrun_01KNJ54X5CJ68C0J837TF2MYTX
  • promiseRaceWorkflow | wrun_01KNJ5520C8VA0BHH2T7NPV0KX
  • promiseAnyWorkflow | wrun_01KNJ555HSBP5XY0KPGHJ05VZK
  • importedStepOnlyWorkflow | wrun_01KNJ56897Q33805VN8NY3W5QX
  • hookWorkflow | wrun_01KNJ55HFY2VTXHZMGRG4YYQGB
  • hookWorkflow is not resumable via public webhook endpoint | wrun_01KNJ55WAEEQC6QZ3XE7B0WCJY
  • webhookWorkflow | wrun_01KNJ5643NY1W8SE6SK8KF7AFH
  • sleepingWorkflow | wrun_01KNJ56AX3NKWPBC06CWQMCW89
  • parallelSleepWorkflow | wrun_01KNJ56PP4R7XC6JMVPBCBJ1GW
  • nullByteWorkflow | wrun_01KNJ56T1B7ZRMHT5WABFGH0FM
  • workflowAndStepMetadataWorkflow | wrun_01KNJ56WA375V4GR9CS746R9JH
  • fetchWorkflow | wrun_01KNJ59GRAHND1YVZ1C0W4PTWJ
  • promiseRaceStressTestWorkflow | wrun_01KNJ59M7EVCWR892ACPN6NFRD
  • 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 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
  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_01KNJ5D4RR2N73MSV01KJN1CWD
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_01KNJ5DSM50HN5QZRD1FCC7VBW
  • hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running | wrun_01KNJ5EF13FQETRMEJJWP6RVNH
  • stepFunctionPassingWorkflow - step function references can be passed as arguments (without closure vars) | wrun_01KNJ5F367XPVGD706S1E3N1EB
  • stepFunctionWithClosureWorkflow - step function with closure variables passed as argument | wrun_01KNJ5FC8B21ENT0TBHB8QFPHX
  • closureVariableWorkflow - nested step functions with closure variables | wrun_01KNJ5FJ5W47T3W9EPQV4BAD62
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step | wrun_01KNJ5FM9EN889VTDE7ECV7ZJS
  • health check (queue-based) - workflow and step endpoints respond to health check messages
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly | wrun_01KNJ5G4EYK8587BQW1ATDQ6RF
  • Calculator.calculate - static workflow method using static step methods from another class | wrun_01KNJ5GABXBCWYMR441N139D75
  • AllInOneService.processNumber - static workflow method using sibling static step methods | wrun_01KNJ5GJGTW6DJ4E7886S0WZBC
  • ChainableService.processWithThis - static step methods using this to reference the class | wrun_01KNJ5GSFKXWFFA4QXXB0V0SM1
  • thisSerializationWorkflow - step function invoked with .call() and .apply() | wrun_01KNJ5H0AXCN82JYF5RW5MZG65
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE | wrun_01KNJ5H73N50ACDA04H1AWKCQ7
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_01KNJ5HDVBFREH0T6KAW43WNE2
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context | wrun_01KNJ5HSHY5CN28MXEYB7WB2DN
  • stepFunctionAsStartArgWorkflow - step function reference passed as start() argument | wrun_01KNJ5J2Y17V4HBZ89NTJJNP54
  • cancelRun - cancelling a running workflow | wrun_01KNJ5J9QJ11K1C2HFZZQTNNRW
  • cancelRun via CLI - cancelling a running workflow | wrun_01KNJ5JKBMWM721K9TC98MSP80
  • pages router addTenWorkflow via pages router
  • pages router promiseAllWorkflow via pages router
  • pages router sleepingWorkflow via pages router
  • hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep | wrun_01KNJ5JZM3V9KKER41SABRHYJW
  • sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration | wrun_01KNJ5KMTC5N6A3T1WBF7JQK1G
  • sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) | wrun_01KNJ5KZ492EDCW5PKHWGK0YH0
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_01KNJ5M5ZMTX0P1C22XP6AY4NT
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_01KNJ5M89896DX2FKAFCY00Q75
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_01KNJ5MACCT496N78HZYYBGMSE

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 79 0 7
✅ example 79 0 7
✅ express 79 0 7
✅ fastify 79 0 7
✅ hono 79 0 7
✅ nextjs-turbopack 84 0 2
✅ nextjs-webpack 84 0 2
✅ nitro 79 0 7
✅ nuxt 79 0 7
✅ sveltekit 79 0 7
✅ vite 79 0 7
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 72 0 14
✅ express-stable 72 0 14
✅ fastify-stable 72 0 14
✅ hono-stable 72 0 14
✅ nextjs-turbopack-canary 61 0 25
✅ nextjs-turbopack-stable 78 0 8
✅ nextjs-webpack-canary 61 0 25
✅ nextjs-webpack-stable 78 0 8
✅ nitro-stable 72 0 14
✅ nuxt-stable 72 0 14
✅ sveltekit-stable 72 0 14
✅ vite-stable 72 0 14
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 72 0 14
✅ express-stable 72 0 14
✅ fastify-stable 72 0 14
✅ hono-stable 72 0 14
✅ nextjs-turbopack-canary 61 0 25
✅ nextjs-turbopack-stable 78 0 8
✅ nextjs-webpack-canary 61 0 25
✅ nextjs-webpack-stable 78 0 8
✅ nitro-stable 72 0 14
✅ nuxt-stable 72 0 14
✅ sveltekit-stable 72 0 14
✅ vite-stable 72 0 14
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 72 0 14
✅ express-stable 72 0 14
✅ fastify-stable 72 0 14
✅ hono-stable 72 0 14
✅ nextjs-turbopack-canary 61 0 25
✅ nextjs-turbopack-stable 78 0 8
✅ nextjs-webpack-canary 61 0 25
✅ nextjs-webpack-stable 78 0 8
✅ nitro-stable 72 0 14
✅ nuxt-stable 72 0 14
✅ sveltekit-stable 72 0 14
✅ vite-stable 72 0 14
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 78 0 8
❌ 🌍 Community Worlds
App Passed Failed Skipped
✅ mongodb-dev 5 0 0
❌ mongodb 57 4 8
✅ redis-dev 5 0 0
❌ redis 58 3 8
✅ turso-dev 5 0 0
❌ turso 4 57 8
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 72 0 14
✅ e2e-local-postgres-nest-stable 72 0 14
✅ e2e-local-prod-nest-stable 72 0 14

📋 View full workflow run

Based on #1354 — adds reader.cancel() in flushablePipe's finally block
so the source stream is notified on disconnect, and adds a cancel()
method to WorkflowServerReadableStream.

Addresses all review feedback: retains original tests, avoids double
cancel, uses .catch(() => {}) instead of console.warn in library code.

Fixes #1349

Signed-off-by: Peter Wielander <mittgfu@gmail.com>
Co-Authored-By: Sigmabrogz <bnb1000bnb@gmail.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Member

@TooTallNate TooTallNate left a comment

Choose a reason for hiding this comment

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

Clean fix for #1349. The two changes work together well: the Promise.race detects premature sink closure, and reader.cancel(reason) propagates the cancellation upstream.

What I verified:

flushablePipePromise.race on reader.read() vs writer.closed:

  • On normal completion: reader.read() returns { done: true }, exits the loop before writer.closed resolves, so the race never triggers the error. Correct.
  • On premature close (e.g. client disconnect, clean close() from external code): writer.closed resolves, throws 'Writable stream closed prematurely', caught by the catch block, sets cancelReason, rejects state.promise. Correct.
  • On writable error (e.g. write failure): writer.closed rejects (not resolves), so Promise.race propagates the rejection directly (bypasses the .then()). Also caught by the catch block. Correct.
  • The writer.closed promise is a persistent getter — no re-allocation per iteration, so the race adds negligible overhead.

flushablePipereader.cancel(cancelReason) in finally:

  • On error: cancelReason is set to the error in catch, reader.cancel(err) propagates context upstream. Correct.
  • On normal completion: cancelReason is undefined, reader.cancel(undefined) is a no-op on an already-done reader per the streams spec. Correct.
  • .catch(() => {}) swallows cancel errors, which is appropriate — cancellation is best-effort.
  • Placed before reader.releaseLock(), which is the right order — cancel before release.

WorkflowServerReadableStreamcancel(reason) method:

  • Propagates cancellation to the inner reader and clears the reference. Correct.
  • .catch(() => {}) on the inner cancel — same best-effort pattern. Correct.

Test coverage:

  • Tests controller.error() propagation through the pipe, verifying state.promise rejects and prior chunks were written. Good coverage for the core scenario.

Changeset: patch for @workflow/core. Correct.

LGTM.

reader.read(),
writer.closed.then(() => {
throw new Error('Writable stream closed prematurely');
}),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Non-blocking observation: writer.closed is a promise that settles at most once. Since the Promise.race is called on every loop iteration, each iteration creates a new .then() handler on the same writer.closed promise. These handlers accumulate but are harmless — once writer.closed settles, they all fire but only the first race winner matters (subsequent iterations would have already exited). In practice, stream iterations are bounded, so this is fine. Just noting for awareness.

@VaguelySerious VaguelySerious merged commit c9b3038 into main Apr 7, 2026
103 of 105 checks passed
@VaguelySerious VaguelySerious deleted the peter/fix-1349-stream-cancel branch April 7, 2026 18:02
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.

run.getReadable()/run.readable do not propagate cancel on disconnect, leaking stream listeners

2 participants