Skip to content

[ci] Fix release "Premature close" by bounding changelog GraphQL batch size - #2669

Merged
VaguelySerious merged 2 commits into
mainfrom
peter/fix-release-changelog-premature-close
Jun 26, 2026
Merged

[ci] Fix release "Premature close" by bounding changelog GraphQL batch size#2669
VaguelySerious merged 2 commits into
mainfrom
peter/fix-release-changelog-premature-close

Conversation

@VaguelySerious

Copy link
Copy Markdown
Member

Problem

The Release workflow's pnpm ci:version (changeset version) step has failed on the last ~10 runs (e.g. run 28264162169) with:

🦋 error FetchError: Invalid response body while trying to fetch https://api.github.com/graphql: Premature close
at Gunzip.<anonymous> (.../node-fetch@2.7.0/.../lib/index.js:217:52)
code: 'ERR_STREAM_PREMATURE_CLOSE'

Root cause

@changesets/changelog-github resolves each changelog entry via @changesets/get-github-info, whose DataLoader is constructed with no maxBatchSize. @changesets/apply-release-plan calls getReleaseLine for every changeset inside one Promise.all, so all commits fold into a single GraphQL query.

We currently have ~300 changesets accumulated (beta pre mode, not yet flushed). That one query grows past what GitHub's GraphQL endpoint will reliably serve. Reproduced against the live API with the exact query shape on Node 24 + node-fetch@2.7.0:

commits in one queryresult
50–290200 OK (~120KB gzip)
300flaky: 200 or broken body / invalid json "<html>…"
350502 Bad Gateway

GitHub returns a truncated/gzip-broken body — which node-fetch@2 surfaces as ERR_STREAM_PREMATURE_CLOSE — or a 502, aborting the whole release. It's deterministic now because the backlog is reliably past the threshold, and it only grows.

The custom .changeset/changelog.mjs wrapper already intends to "never want a flaky network call to break pnpm changeset version", but only guarded its own backport lookups — not the upstream.getReleaseLine delegation that runs node-fetch.

Fix

  1. Bound the batch size via a concurrency gate (25). Limiting how many upstream calls are in flight bounds the number of get-github-info.load() calls per tick, which bounds the DataLoader batch — so the work splits into several small queries GitHub serves reliably, preserving full PR/author attribution.
  2. Wrap the upstream delegation in try/catch with a network-free fallback line, so a transient failure on a single batch degrades one entry instead of failing the release (finally extends the wrapper's stated promise to the delegation path).

Validation (empirical, against the live GitHub API)

  • Ungated → 1 request of 120; gated p-limit(25)5 requests, max batch 25.
  • Through the real modified wrapper over 120 commits: 5 requests, max batch 25, all 120 lines retain their [\sha`](…)commit link +Thanks @…` attribution.
  • Forcing node-fetch to throw ERR_STREAM_PREMATURE_CLOSE: no throw, well-formed offline line with commit link + multi-line summary.

🤖 Generated with Claude Code

…h size
The Release workflow's `changeset version` step has failed on the last ~10
runs with `FetchError: ... api.github.com/graphql: Premature close`.
`@changesets/changelog-github` resolves each entry via
`@changesets/get-github-info`, whose DataLoader is created with no
`maxBatchSize`. `apply-release-plan` calls `getReleaseLine` for every
changeset in one `Promise.all`, so all commits fold into a single GraphQL
query. With ~300 changesets accumulated in beta pre mode, that query grows
past what GitHub's GraphQL endpoint reliably serves — it returns a
truncated/gzip-broken body (which node-fetch@2 surfaces as
ERR_STREAM_PREMATURE_CLOSE) or a 502, aborting the whole release.
The custom `.changeset/changelog.mjs` wrapper already aimed to never let a
network call break the release, but only guarded its own backport lookups,
not the upstream delegation that runs node-fetch.
Fix:
- Cap concurrent upstream calls (25) so the DataLoader batches split across
several small queries GitHub can serve, preserving full PR/author
attribution. Verified against the live API: 120 commits -> 5 requests of
<=25 instead of one batch of 120.
- Wrap the upstream delegation in try/catch with a network-free fallback
line, so a transient failure on one batch degrades one entry instead of
failing the release.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code ownersJune 26, 2026 21:09
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fcd41ca

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

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

vercelBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production144202301672
✅ 💻 Local Development160502191824
✅ 📦 Local Production160502191824
✅ 🐘 Local Postgres159302311824
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total7282010788360

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-turbopack14903
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable15101
✅ nitro-stable126026
✅ nuxt-stable126026
✅ sveltekit-stable14507
✅ vite-stable126026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable127025
✅ e2e-local-dev-tanstack-start-127025
✅ e2e-local-postgres-nest-stable126026
✅ e2e-local-postgres-tanstack-start-126026
✅ e2e-local-prod-nest-stable127025
✅ e2e-local-prod-tanstack-start-127025
✅ e2e-vercel-prod-tanstack-start125027

📋 View full workflow run

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.048s (+3.7%)1.006s (~)0.958s101.00x
💻 LocalNitro0.048s (+8.5% 🔺)1.006s (~)0.958s101.00x
💻 LocalNext.js (Turbopack)0.049s (~)1.006s (~)0.956s101.03x
🐘 PostgresNext.js (Turbopack)0.055s (-9.1% 🟢)1.011s (~)0.957s101.14x
🐘 PostgresExpress0.065s (-13.6% 🟢)1.012s (~)0.947s101.35x
🐘 PostgresNitro0.072s (~)1.013s (~)0.941s101.49x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.204s (-27.8% 🟢)1.652s (-13.6% 🟢)1.448s101.00x
▲ VercelNext.js (Turbopack)0.548s (-10.5% 🟢)2.528s (~)1.980s102.69x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.082s (~)2.006s (~)0.924s101.00x
💻 LocalExpress1.083s (+0.7%)2.007s (~)0.924s101.00x
🐘 PostgresNext.js (Turbopack)1.085s (-0.9%)2.010s (~)0.926s101.00x
💻 LocalNext.js (Turbopack)1.085s (+0.5%)2.006s (~)0.921s101.00x
🐘 PostgresExpress1.095s (~)2.010s (~)0.915s101.01x
🐘 PostgresNitro1.102s (+0.5%)2.010s (~)0.908s101.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.393s (+2.1%)3.035s (+0.9%)1.641s101.00x
▲ VercelNext.js (Turbopack)2.053s (-12.7% 🟢)3.736s (-0.7%)1.682s101.47x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.446s (~)11.022s (~)0.576s31.00x
💻 LocalExpress10.459s (~)11.024s (~)0.565s31.00x
🐘 PostgresNext.js (Turbopack)10.465s (-0.6%)11.015s (~)0.549s31.00x
💻 LocalNext.js (Turbopack)10.469s (~)11.023s (~)0.554s31.00x
🐘 PostgresNitro10.492s (~)11.018s (~)0.526s31.00x
🐘 PostgresExpress10.502s (~)11.020s (~)0.518s31.01x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express12.061s (+2.7%)14.083s (+4.4%)2.022s31.00x
▲ VercelNext.js (Turbopack)13.273s (+4.5%)15.530s (+5.7% 🔺)2.258s21.10x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)13.578s (~)14.027s (~)0.449s51.00x
💻 LocalNitro13.613s (~)14.027s (~)0.414s51.00x
💻 LocalExpress13.631s (+0.5%)14.027s (~)0.397s51.00x
🐘 PostgresNitro13.683s (~)14.020s (~)0.337s51.01x
🐘 PostgresExpress13.766s (+0.6%)14.016s (~)0.250s51.01x
🐘 PostgresNext.js (Turbopack)13.783s (~)14.219s (+1.4%)0.436s51.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express18.000s (+9.3% 🔺)20.026s (+9.4% 🔺)2.026s41.00x
▲ VercelNext.js (Turbopack)18.839s (+6.9% 🔺)20.609s (+6.5% 🔺)1.770s31.05x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express12.073s (-3.0%)12.875s (-1.2%)0.802s71.00x
💻 LocalNitro12.126s (+0.9%)13.026s (+3.0%)0.901s71.00x
💻 LocalNext.js (Turbopack)12.134s (-3.1%)13.025s (-1.1%)0.892s71.01x
💻 LocalExpress12.203s (+1.6%)13.027s (+6.1% 🔺)0.824s71.01x
🐘 PostgresNext.js (Turbopack)12.229s (-2.2%)13.017s (~)0.788s71.01x
🐘 PostgresNitro12.302s (-0.9%)13.022s (~)0.720s71.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express19.662s (+7.8% 🔺)21.742s (+8.4% 🔺)2.080s51.00x
▲ VercelNext.js (Turbopack)21.512s (+1.3%)23.389s (+1.6%)1.877s41.09x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.166s (-0.8%)2.008s (~)0.842s151.00x
🐘 PostgresNitro1.182s (-0.6%)2.008s (~)0.825s151.01x
🐘 PostgresNext.js (Turbopack)1.191s (-1.6%)2.007s (-3.3%)0.816s151.02x
💻 LocalNitro1.400s (+1.8%)2.006s (~)0.606s151.20x
💻 LocalExpress1.421s (+1.9%)2.007s (~)0.586s151.22x
💻 LocalNext.js (Turbopack)1.472s (+4.7%)2.007s (~)0.535s151.26x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.561s (+18.3% 🔺)4.068s (+5.3% 🔺)1.508s81.00x
▲ VercelNext.js (Turbopack)4.041s (+25.4% 🔺)5.752s (+17.5% 🔺)1.711s61.58x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.300s (-2.4%)3.008s (~)1.708s101.00x
🐘 PostgresExpress1.331s (-1.3%)2.317s (-7.7% 🟢)0.985s131.02x
🐘 PostgresNitro1.354s (+2.3%)2.394s (-12.5% 🟢)1.040s131.04x
💻 LocalNitro2.447s (-0.7%)2.918s (-3.0%)0.470s111.88x
💻 LocalExpress2.781s (+12.0% 🔺)3.109s (+3.3%)0.328s102.14x
💻 LocalNext.js (Turbopack)2.807s (+16.9% 🔺)3.109s (+3.3%)0.302s102.16x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.787s (+10.9% 🔺)4.403s (+8.4% 🔺)1.616s71.00x
▲ VercelNext.js (Turbopack)3.872s (+8.7% 🔺)6.078s (+16.8% 🔺)2.207s51.39x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.612s (~)4.012s (-5.9% 🟢)2.399s81.00x
🐘 PostgresNitro1.670s (~)4.441s (-6.1% 🟢)2.770s71.04x
🐘 PostgresNext.js (Turbopack)2.288s (-29.4% 🟢)5.182s (-13.9% 🟢)2.894s61.42x
💻 LocalNitro6.144s (-1.0%)6.816s (-2.8%)0.672s53.81x
💻 LocalExpress6.790s (+13.7% 🔺)7.416s (+15.6% 🔺)0.626s54.21x
💻 LocalNext.js (Turbopack)6.878s (+15.4% 🔺)8.268s (+14.6% 🔺)1.391s44.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express4.610s (+67.5% 🔺)6.681s (+53.2% 🔺)2.070s51.00x
▲ VercelNext.js (Turbopack)5.407s (+22.7% 🔺)7.280s (+16.6% 🔺)1.873s51.17x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.177s (~)2.008s (~)0.831s151.00x
🐘 PostgresNitro1.189s (+0.6%)2.008s (~)0.819s151.01x
🐘 PostgresNext.js (Turbopack)1.195s (~)2.006s (~)0.812s151.02x
💻 LocalNitro1.415s (+1.2%)2.007s (~)0.592s151.20x
💻 LocalExpress1.416s (+1.4%)2.007s (~)0.590s151.20x
💻 LocalNext.js (Turbopack)1.443s (~)2.006s (~)0.563s151.23x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.246s (+8.3% 🔺)3.921s (+6.6% 🔺)1.674s81.00x
▲ VercelNext.js (Turbopack)3.820s (+19.2% 🔺)5.341s (+10.0% 🔺)1.521s61.70x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.291s (-3.8%)2.316s (+0.9%)1.025s131.00x
🐘 PostgresNext.js (Turbopack)1.294s (-1.6%)3.010s (~)1.716s101.00x
🐘 PostgresNitro1.347s (+4.2%)2.317s (~)0.970s131.04x
💻 LocalNitro2.539s (+5.3% 🔺)3.009s (~)0.470s101.97x
💻 LocalExpress2.694s (+11.9% 🔺)3.009s (~)0.315s102.09x
💻 LocalNext.js (Turbopack)2.710s (+2.7%)3.010s (-3.2%)0.300s102.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.881s (+16.6% 🔺)4.624s (+20.8% 🔺)1.743s71.00x
▲ VercelNext.js (Turbopack)4.760s (+39.8% 🔺)6.645s (+38.4% 🔺)1.886s51.65x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.637s (+0.5%)4.447s (+10.9% 🔺)2.811s71.00x
🐘 PostgresNitro1.879s (+14.0% 🔺)4.870s (+17.6% 🔺)2.991s71.15x
🐘 PostgresNext.js (Turbopack)3.326s (+4.1%)6.215s (~)2.889s52.03x
💻 LocalNext.js (Turbopack)6.042s (+7.0% 🔺)6.618s (+6.5% 🔺)0.575s53.69x
💻 LocalExpress6.361s (+14.7% 🔺)7.017s (+12.9% 🔺)0.655s53.89x
💻 LocalNitro7.257s (+22.0% 🔺)8.020s (+24.9% 🔺)0.763s44.43x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.768s (+38.5% 🔺)5.671s (+29.3% 🔺)1.903s61.00x
▲ VercelNext.js (Turbopack)4.935s (-8.6% 🟢)6.674s (-6.1% 🟢)1.738s51.31x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.511s (-17.2% 🟢)1.006s (-1.9%)0.495s601.00x
🐘 PostgresExpress0.551s (-5.4% 🟢)1.023s (-1.8%)0.472s591.08x
🐘 PostgresNitro0.584s (+1.0%)1.006s (-1.6%)0.422s601.14x
💻 LocalNitro0.585s (~)1.022s (~)0.437s591.15x
💻 LocalExpress0.590s (+5.8% 🔺)1.005s (~)0.416s601.15x
💻 LocalNext.js (Turbopack)0.611s (+2.0%)1.022s (+1.7%)0.411s591.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.182s (+29.4% 🔺)5.219s (+35.4% 🔺)2.037s121.00x
▲ VercelNext.js (Turbopack)4.964s (+32.2% 🔺)6.751s (+30.3% 🔺)1.786s91.56x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.229s (-11.6% 🟢)2.007s (~)0.778s451.00x
🐘 PostgresExpress1.345s (-3.1%)2.053s (~)0.708s441.09x
🐘 PostgresNitro1.412s (+7.7% 🔺)2.054s (+2.3%)0.642s441.15x
💻 LocalNitro1.478s (+3.6%)2.006s (~)0.528s451.20x
💻 LocalExpress1.492s (+3.0%)2.006s (-1.1%)0.514s451.21x
💻 LocalNext.js (Turbopack)1.498s (~)2.006s (~)0.508s451.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express8.097s (+38.5% 🔺)10.089s (+35.8% 🔺)1.992s91.00x
▲ VercelNext.js (Turbopack)9.498s (+12.5% 🔺)11.514s (+13.2% 🔺)2.017s81.17x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)2.413s (-13.6% 🟢)3.008s (-3.4%)0.594s401.00x
🐘 PostgresExpress2.517s (-7.9% 🟢)3.034s (-1.7%)0.517s401.04x
🐘 PostgresNitro2.726s (+0.7%)3.086s (-1.6%)0.360s391.13x
💻 LocalNitro3.138s (-2.1%)3.913s (+2.4%)0.775s311.30x
💻 LocalNext.js (Turbopack)3.225s (-1.7%)3.978s (-0.8%)0.753s311.34x
💻 LocalExpress3.300s (+4.4%)4.010s (+5.8% 🔺)0.710s301.37x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express18.146s (+52.1% 🔺)20.493s (+46.9% 🔺)2.347s71.00x
▲ VercelNext.js (Turbopack)22.977s (+21.7% 🔺)24.987s (+19.6% 🔺)2.011s51.27x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.191s (+1.6%)1.023s (+1.5%)0.832s591.00x
🐘 PostgresExpress0.201s (-9.8% 🟢)1.006s (~)0.805s601.05x
🐘 PostgresNitro0.214s (-1.8%)1.006s (~)0.792s601.12x
💻 LocalNitro0.462s (+7.7% 🔺)1.005s (~)0.543s602.41x
💻 LocalExpress0.483s (+13.1% 🔺)1.005s (~)0.522s602.52x
💻 LocalNext.js (Turbopack)0.628s (+6.0% 🔺)1.022s (+1.7%)0.394s593.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.370s (+19.6% 🔺)2.945s (+15.5% 🔺)1.575s211.00x
▲ VercelNext.js (Turbopack)2.815s (+36.1% 🔺)4.511s (+21.4% 🔺)1.696s142.05x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.270s (-9.4% 🟢)1.006s (-1.2%)0.736s901.00x
🐘 PostgresExpress0.318s (-5.9% 🟢)1.006s (-1.1%)0.688s901.18x
🐘 PostgresNitro0.345s (+1.2%)1.007s (~)0.662s901.28x
💻 LocalNitro2.079s (-4.1%)2.686s (-4.0%)0.607s347.71x
💻 LocalExpress2.164s (+2.0%)2.767s (+1.1%)0.603s338.02x
💻 LocalNext.js (Turbopack)2.792s (+7.0% 🔺)3.258s (+4.6%)0.466s2810.35x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.990s (+36.3% 🔺)3.997s (+30.3% 🔺)2.006s231.00x
▲ VercelNext.js (Turbopack)3.585s (+23.3% 🔺)5.408s (+12.6% 🔺)1.823s171.80x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.478s (-9.6% 🟢)3.010s (-1.7%)2.532s401.00x
🐘 PostgresExpress0.532s (-2.6%)1.149s (+3.7%)0.618s1051.11x
🐘 PostgresNitro0.545s (-3.0%)1.050s (-3.5%)0.505s1151.14x
💻 LocalNitro10.004s (+6.8% 🔺)11.031s (+5.6% 🔺)1.027s1120.93x
💻 LocalNext.js (Turbopack)10.057s (+6.1% 🔺)11.211s (+6.5% 🔺)1.155s1121.04x
💻 LocalExpress10.417s (+13.4% 🔺)11.305s (+9.1% 🔺)0.887s1121.79x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.916s (+52.4% 🔺)4.704s (+22.2% 🔺)1.788s261.00x
▲ VercelNext.js (Turbopack)5.493s (+46.5% 🔺)7.471s (+35.5% 🔺)1.979s171.88x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.137s (-1.3%)1.969s (~)0.012s (+17.0% 🔺)2.020s (~)0.883s101.00x
🐘 PostgresExpress1.145s (-1.1%)2.000s (~)0.001s (-21.4% 🟢)2.010s (~)0.865s101.01x
🐘 PostgresNext.js (Turbopack)1.153s (-1.0%)2.000s (~)0.001s (+30.0% 🔺)2.010s (~)0.857s101.01x
💻 LocalNitro1.160s (+0.8%)2.004s (~)0.012s (+9.7% 🔺)2.020s (~)0.859s101.02x
💻 LocalExpress1.164s (+1.1%)2.005s (~)0.012s (+22.8% 🔺)2.021s (~)0.856s101.02x
🐘 PostgresNitro1.166s (-2.1%)1.999s (~)0.001s (-26.7% 🟢)2.011s (~)0.845s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.974s (-7.9% 🟢)3.569s (+9.0% 🔺)2.038s (+21.1% 🔺)6.129s (+12.0% 🔺)4.155s101.00x
▲ VercelNext.js (Turbopack)3.986s (+28.8% 🔺)4.016s (+6.1% 🔺)1.953s (+42.3% 🔺)7.748s (+23.1% 🔺)3.761s102.02x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.540s (-0.8%)2.003s (~)0.005s (+4.7%)2.026s (~)0.486s301.00x
💻 LocalNitro1.547s (+0.6%)2.009s (~)0.013s (+3.7%)2.025s (~)0.479s301.00x
🐘 PostgresNext.js (Turbopack)1.558s (-3.7%)2.009s (~)0.005s (+2.6%)2.026s (~)0.467s301.01x
💻 LocalExpress1.574s (+2.7%)2.011s (~)0.013s (+0.8%)2.026s (~)0.453s301.02x
💻 LocalNext.js (Turbopack)1.584s (~)1.971s (~)0.011s (-23.6% 🟢)2.024s (~)0.441s301.03x
🐘 PostgresNitro1.597s (+0.7%)2.002s (~)0.005s (+3.4%)2.026s (~)0.428s301.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express6.367s (+16.0% 🔺)7.885s (+13.5% 🔺)0.250s (+8.7% 🔺)8.629s (+11.5% 🔺)2.261s71.00x
▲ VercelNext.js (Turbopack)10.932s (+18.7% 🔺)11.820s (+8.5% 🔺)0.243s (-3.3%)13.252s (+14.1% 🔺)2.320s51.72x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.769s (-6.4% 🟢)1.065s (~)0.000s (-32.1% 🟢)1.102s (+2.2%)0.333s551.00x
🐘 PostgresNitro0.806s (-1.8%)1.045s (-5.2% 🟢)0.000s (-36.8% 🟢)1.060s (-5.4% 🟢)0.253s571.05x
🐘 PostgresNext.js (Turbopack)1.006s (+0.8%)1.418s (-2.9%)0.000s (-100.0% 🟢)1.429s (-3.7%)0.422s431.31x
💻 LocalExpress1.240s (+0.6%)2.013s (~)0.000s (-60.0% 🟢)2.015s (~)0.776s301.61x
💻 LocalNitro1.268s (~)2.013s (~)0.000s (+14.3% 🔺)2.016s (~)0.748s301.65x
💻 LocalNext.js (Turbopack)1.354s (-2.9%)1.979s (~)0.001s (+77.8% 🔺)2.016s (~)0.662s301.76x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.701s (+27.1% 🔺)5.023s (+15.1% 🔺)0.002s (+Infinity% 🔺)5.526s (+14.0% 🔺)1.825s111.00x
▲ VercelNext.js (Turbopack)5.168s (+6.3% 🔺)6.181s (+10.6% 🔺)0.000s (NaN%)6.952s (+1.2%)1.784s91.40x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.703s (+0.7%)2.301s (~)0.000s (+Infinity% 🔺)2.316s (~)0.613s261.00x
🐘 PostgresNitro1.770s (-3.3%)2.301s (-3.2%)0.000s (-100.0% 🟢)2.332s (-3.0%)0.562s261.04x
🐘 PostgresNext.js (Turbopack)2.551s (-17.5% 🟢)3.051s (-13.6% 🟢)0.000s (-71.7% 🟢)3.059s (-13.6% 🟢)0.508s201.50x
💻 LocalNitro3.469s (+5.1% 🔺)4.095s (+1.7%)0.000s (-30.0% 🟢)4.099s (+1.7%)0.630s152.04x
💻 LocalExpress3.538s (+7.8% 🔺)4.027s (+4.9%)0.001s (+113.3% 🔺)4.030s (+4.9%)0.492s152.08x
💻 LocalNext.js (Turbopack)3.541s (~)4.058s (+1.7%)0.001s (+113.3% 🔺)4.098s (+1.4%)0.557s152.08x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.371s (+2.8%)6.997s (+7.5% 🔺)0.000s (-100.0% 🟢)7.482s (+7.0% 🔺)2.111s91.00x
▲ VercelNext.js (Turbopack)8.546s (+24.9% 🔺)8.850s (+15.0% 🔺)0.000s (-100.0% 🟢)10.343s (+19.1% 🔺)1.797s61.59x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro16/21
🐘 PostgresNext.js (Turbopack)10/21
▲ VercelExpress21/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres17/21
Next.js (Turbopack)🐘 Postgres17/21
Nitro🐘 Postgres14/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)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

Comment thread.changeset/changelog.mjs Outdated
…tten summary verbatim, leaking injected `pr:`/`commit:` directive lines into the changelog so the entry title becomes literally `pr: #N`.
This commit fixes the issue reported at .changeset/changelog.mjs:368
## Bug
`maybeRewriteChangesetForBackport` prepends directive lines to a backport changeset's summary:
```js
const rewrittenSummary =
`pr: #${originalPRNumber}\n` +
`commit: ${changeset.commit}\n` +
summary;
return { ...changeset, summary: rewrittenSummary };
```
The upstream `@changesets/changelog-github` `getReleaseLine` consumes those directives and **strips them** from the summary (via `.replace(/^\s*(?:pr|pull|pull\s+request):.../im, ...)`, `commit:`, `author:` etc.) before splitting into `firstLine`/`futureLines` and rendering. So in the normal path the directives never appear in the output — that's the whole point of injecting them.
The offline fallback, however, does **not** strip them:
```js
function offlineReleaseLine(changeset, options) {
const [firstLine, ...futureLines] = (changeset.summary ?? '')
.split('\n')
.map((l) => l.trimEnd());
...
}
```
It just splits on `\n` and renders verbatim.
### Concrete trigger
A backport changeset where:
1. `maybeRewriteChangesetForBackport` **succeeds** (network up, PR resolved, directives injected → `rewritten.summary` starts with `pr: #N\ncommit: <sha>\n`), and
2. the subsequent `limitUpstream(() => upstream.getReleaseLine(rewritten, ...))` **throws** — exactly the flaky/oversized GraphQL batch scenario this fallback was added for.
The catch block then ran `offlineReleaseLine(rewritten, options)`, producing:
```
- [`abc1234`](...) - pr: #123
commit: abc1234...
<real first line>
```
The entry title becomes literally `pr: #123` and `commit: …` leaks as body text.
## Fix
Pass the **original** `changeset` to `offlineReleaseLine` instead of `rewritten`. The backport rewrite only mutates `summary`; `changeset.commit` is untouched, so the offline commit link is identical, while the original summary has no injected directives. The offline path already drops PR/author attribution it can't resolve, so using the un-rewritten summary is exactly correct.
The dependency-line path (`offlineDependencyReleaseLine`) is unaffected since no directive injection happens there.
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: VaguelySerious <mittgfu@gmail.com>

@pranaygppranaygp 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.

Reviewed the diff and verified the core mechanism end to end against the real file (stubbed upstream, Node 22.18, require()-able ESM confirmed):

  • Limiter works as claimed: 120 getReleaseLine calls → max 25 concurrent → ~5 DataLoader batches of ≤25, order preserved. Since get-github-info's DataLoader is a module-level singleton built with no maxBatchSize (confirmed in get-github-info@0.7.0 src) and each getReleaseLine fires exactly one .load(), gating concurrency at 25 genuinely bounds the per-batch query size while preserving full PR/author attribution. Good, well-reasoned fix.
  • Offline getReleaseLine mirrors the upstream template exactly (changelog-github@0.5.2): `\n\n-${prefix ? ` ${prefix} -` : ''} ${firstLine}...`, dropping only the pull/Thanks attribution. The 2nd-commit fix (feed the original changeset, not rewritten) is correct and I confirmed it no longer leaks pr:/commit: directives.
  • Offline dependency line is lossless — dep lines only ever render commit links, which you reconstruct from the SHA, so that path loses nothing even when it falls back.

LGTM to ship — it unblocks the release. A few non-blocking notes inline (one substantive: the dependency-rollup path isn't actually batch-bounded by the limiter and is carried entirely by the fallback).

// truncated/gzip-broken body (which `node-fetch@2` surfaces as
// `ERR_STREAM_PREMATURE_CLOSE`) or a 502, aborting the whole release.
// 6. Wraps the upstream delegation in a try/catch with a network-free
// fallback line, so a transient failure on one batch degrades a single

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.

Minor wording: "degrades a single entry" undersells it. When an oversized getReleaseLine batch fails, every .load() sharing that one fetch rejects together, so a whole batch (~UPSTREAM_CONCURRENCY, up to 25 entries) drops to the offline line at once — not one. The important half ("instead of failing the release") still holds; just the count is off.

options,
);
try {
return await limitUpstream(() =>

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.

The concurrency gate bounds the getReleaseLine batch nicely, but it does not bound this path's batch. apply-release-plan's getChangelogEntry calls getDependencyReleaseLine once per release with relevantChangesets (every changeset that bumped that package's deps), and upstream fans out to one getInfo() per changeset inside a single internal Promise.all — so all of those .load()s land in one DataLoader batch within this one limiter slot. A package whose deps were bumped by hundreds of changesets (e.g. the many packages depending on @workflow/core) therefore still produces one oversized query here, exactly the shape this PR is fixing.

In practice the release stays safe: that query throws → catchofflineDependencyReleaseLine, which is lossless for dependency lines (they only render commit links). So no functional gap. But point 5's rationale — "splits its lookups across several small GraphQL queries" — only really applies to getReleaseLine; this path is carried by the fallback, not by batch-splitting. Worth a word in the comments so the next reader doesn't assume both paths are bounded (or, if you want it truly bounded, gate the per-getInfo fan-out rather than the outer call).

// the changelog (the entry title would become literally `pr: #N`). The
// rewrite only touches `summary`; `changeset.commit` is unchanged, so the
// offline commit link is identical either way.
return offlineReleaseLine(changeset, options);

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.

Good fix on the backport-rewrite leak. One residual of the same class: if a changeset's summary manually contains a pr:/commit:/author: directive, maybeRewriteChangesetForBackport returns it unchanged (the guard at L309), upstream would strip it before rendering — but the offline path renders it verbatim, so the entry title becomes literally pr: #123 (confirmed locally). Much rarer than the backport case (needs a hand-written directive and an upstream failure), so not a blocker. If you want the offline path to fully match upstream, strip the leading directive lines here the same way upstream's .replace(/^\s*(?:pr|pull|...|author|user):/im, '') chain does.

: ` \`${short}\``;
}

function offlineReleaseLine(changeset, options) {

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.

Optional: these offline helpers + the limiter are pure and easy to unit-test, and the directive leak already regressed once (needed a follow-up commit), which is a good signal this file is edit-fragile. A tiny test — force upstream to throw and assert the offline line keeps the commit link + a clean title, plus assert 120 calls never exceed 25 concurrent — would be cheap regression insurance. No test runner is wired for .changeset/ today, so low priority, but worth considering.

@VaguelySerious
VaguelySerious merged commit 7f04d87 into mainJun 26, 2026
112 of 113 checks passed
@VaguelySerious
VaguelySerious deleted the peter/fix-release-changelog-premature-close branch June 26, 2026 21:42
github-actionsBot added a commit that referenced this pull request Jun 26, 2026
…h size (#2669)
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #2671. (backport job run)

VaguelySerious added a commit that referenced this pull request Jun 26, 2026
…h size (#2669) (#2671)
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
pranaygp added a commit that referenced this pull request Jun 26, 2026
…edrive
* origin/main: (90 commits)
[ai] Fix "missing text part" stream crash by repairing UI message framing (#2537)
[ci] Fix release "Premature close" by bounding changelog GraphQL batch size (#2669)
[ai] Drop orphan UI chunks after negative-index reconnect (#2082)
Fix trace detail panel Input/Output flicker (selection-driven state machine) (#2637)
[world-local] Scope untagged startup recovery to untagged runs (#2667)
fix(world-postgres): defer loopback worker startup (#2657)
docs: deprecate DurableAgent and WorkflowChatTransport, point to AI SDK WorkflowAgent (#2616)
[web] Fix HTTP/2 in bundled server build so observability reads work (#2632)
[core] Fix turbo-mode step-body writes racing run_started (#2629)
fix(world-postgres): rename setup command (#2644)
chore: ignore workflow swc caches (#2640)
fix(ai): ignore intentional stream aborts (#2635)
fix(nitro): reload steps during Vite HMR (#2572)
[world] Make @workflow/world own its Zod dependency (#2622)
feat(web-shared): extend cn with custom tailwind-merge class groups (#2619)
[world-vercel] Enable HTTP/2 for the events API and stream writes (#2573)
Remove shadow from off-screen marker indicator button (#2614)
Fix discovery of dotted JS workflow imports (#2594)
Version Packages (beta) (#2597)
Thread occurredAt into web-shared entities (#2608)
...
# Conflicts:
#	packages/core/src/runtime/step-executor.ts
#	packages/world-vercel/src/http-client.ts
Sign up for freeto 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.

2 participants

@VaguelySerious@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
[ci] Fix release "Premature close" by bounding changelog GraphQL batch size by VaguelySerious · Pull Request #2669 · vercel/workflow · GitHub
Skip to content

[ci] Fix release "Premature close" by bounding changelog GraphQL batch size - #2669

Merged
VaguelySerious merged 2 commits into
mainfrom
peter/fix-release-changelog-premature-close
Jun 26, 2026
Merged

[ci] Fix release "Premature close" by bounding changelog GraphQL batch size#2669
VaguelySerious merged 2 commits into
mainfrom
peter/fix-release-changelog-premature-close

Conversation

@VaguelySerious

Copy link
Copy Markdown
Member

Problem

The Release workflow's pnpm ci:version (changeset version) step has failed on the last ~10 runs (e.g. run 28264162169) with:

🦋 error FetchError: Invalid response body while trying to fetch https://api.github.com/graphql: Premature close
at Gunzip.<anonymous> (.../node-fetch@2.7.0/.../lib/index.js:217:52)
code: 'ERR_STREAM_PREMATURE_CLOSE'

Root cause

@changesets/changelog-github resolves each changelog entry via @changesets/get-github-info, whose DataLoader is constructed with no maxBatchSize. @changesets/apply-release-plan calls getReleaseLine for every changeset inside one Promise.all, so all commits fold into a single GraphQL query.

We currently have ~300 changesets accumulated (beta pre mode, not yet flushed). That one query grows past what GitHub's GraphQL endpoint will reliably serve. Reproduced against the live API with the exact query shape on Node 24 + node-fetch@2.7.0:

commits in one queryresult
50–290200 OK (~120KB gzip)
300flaky: 200 or broken body / invalid json "<html>…"
350502 Bad Gateway

GitHub returns a truncated/gzip-broken body — which node-fetch@2 surfaces as ERR_STREAM_PREMATURE_CLOSE — or a 502, aborting the whole release. It's deterministic now because the backlog is reliably past the threshold, and it only grows.

The custom .changeset/changelog.mjs wrapper already intends to "never want a flaky network call to break pnpm changeset version", but only guarded its own backport lookups — not the upstream.getReleaseLine delegation that runs node-fetch.

Fix

  1. Bound the batch size via a concurrency gate (25). Limiting how many upstream calls are in flight bounds the number of get-github-info.load() calls per tick, which bounds the DataLoader batch — so the work splits into several small queries GitHub serves reliably, preserving full PR/author attribution.
  2. Wrap the upstream delegation in try/catch with a network-free fallback line, so a transient failure on a single batch degrades one entry instead of failing the release (finally extends the wrapper's stated promise to the delegation path).

Validation (empirical, against the live GitHub API)

  • Ungated → 1 request of 120; gated p-limit(25)5 requests, max batch 25.
  • Through the real modified wrapper over 120 commits: 5 requests, max batch 25, all 120 lines retain their [\sha`](…)commit link +Thanks @…` attribution.
  • Forcing node-fetch to throw ERR_STREAM_PREMATURE_CLOSE: no throw, well-formed offline line with commit link + multi-line summary.

🤖 Generated with Claude Code

…h size
The Release workflow's `changeset version` step has failed on the last ~10
runs with `FetchError: ... api.github.com/graphql: Premature close`.
`@changesets/changelog-github` resolves each entry via
`@changesets/get-github-info`, whose DataLoader is created with no
`maxBatchSize`. `apply-release-plan` calls `getReleaseLine` for every
changeset in one `Promise.all`, so all commits fold into a single GraphQL
query. With ~300 changesets accumulated in beta pre mode, that query grows
past what GitHub's GraphQL endpoint reliably serves — it returns a
truncated/gzip-broken body (which node-fetch@2 surfaces as
ERR_STREAM_PREMATURE_CLOSE) or a 502, aborting the whole release.
The custom `.changeset/changelog.mjs` wrapper already aimed to never let a
network call break the release, but only guarded its own backport lookups,
not the upstream delegation that runs node-fetch.
Fix:
- Cap concurrent upstream calls (25) so the DataLoader batches split across
several small queries GitHub can serve, preserving full PR/author
attribution. Verified against the live API: 120 commits -> 5 requests of
<=25 instead of one batch of 120.
- Wrap the upstream delegation in try/catch with a network-free fallback
line, so a transient failure on one batch degrades one entry instead of
failing the release.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code ownersJune 26, 2026 21:09
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fcd41ca

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

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

vercelBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production144202301672
✅ 💻 Local Development160502191824
✅ 📦 Local Production160502191824
✅ 🐘 Local Postgres159302311824
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total7282010788360

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-turbopack14903
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable15101
✅ nitro-stable126026
✅ nuxt-stable126026
✅ sveltekit-stable14507
✅ vite-stable126026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable127025
✅ e2e-local-dev-tanstack-start-127025
✅ e2e-local-postgres-nest-stable126026
✅ e2e-local-postgres-tanstack-start-126026
✅ e2e-local-prod-nest-stable127025
✅ e2e-local-prod-tanstack-start-127025
✅ e2e-vercel-prod-tanstack-start125027

📋 View full workflow run

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.048s (+3.7%)1.006s (~)0.958s101.00x
💻 LocalNitro0.048s (+8.5% 🔺)1.006s (~)0.958s101.00x
💻 LocalNext.js (Turbopack)0.049s (~)1.006s (~)0.956s101.03x
🐘 PostgresNext.js (Turbopack)0.055s (-9.1% 🟢)1.011s (~)0.957s101.14x
🐘 PostgresExpress0.065s (-13.6% 🟢)1.012s (~)0.947s101.35x
🐘 PostgresNitro0.072s (~)1.013s (~)0.941s101.49x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.204s (-27.8% 🟢)1.652s (-13.6% 🟢)1.448s101.00x
▲ VercelNext.js (Turbopack)0.548s (-10.5% 🟢)2.528s (~)1.980s102.69x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.082s (~)2.006s (~)0.924s101.00x
💻 LocalExpress1.083s (+0.7%)2.007s (~)0.924s101.00x
🐘 PostgresNext.js (Turbopack)1.085s (-0.9%)2.010s (~)0.926s101.00x
💻 LocalNext.js (Turbopack)1.085s (+0.5%)2.006s (~)0.921s101.00x
🐘 PostgresExpress1.095s (~)2.010s (~)0.915s101.01x
🐘 PostgresNitro1.102s (+0.5%)2.010s (~)0.908s101.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.393s (+2.1%)3.035s (+0.9%)1.641s101.00x
▲ VercelNext.js (Turbopack)2.053s (-12.7% 🟢)3.736s (-0.7%)1.682s101.47x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.446s (~)11.022s (~)0.576s31.00x
💻 LocalExpress10.459s (~)11.024s (~)0.565s31.00x
🐘 PostgresNext.js (Turbopack)10.465s (-0.6%)11.015s (~)0.549s31.00x
💻 LocalNext.js (Turbopack)10.469s (~)11.023s (~)0.554s31.00x
🐘 PostgresNitro10.492s (~)11.018s (~)0.526s31.00x
🐘 PostgresExpress10.502s (~)11.020s (~)0.518s31.01x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express12.061s (+2.7%)14.083s (+4.4%)2.022s31.00x
▲ VercelNext.js (Turbopack)13.273s (+4.5%)15.530s (+5.7% 🔺)2.258s21.10x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)13.578s (~)14.027s (~)0.449s51.00x
💻 LocalNitro13.613s (~)14.027s (~)0.414s51.00x
💻 LocalExpress13.631s (+0.5%)14.027s (~)0.397s51.00x
🐘 PostgresNitro13.683s (~)14.020s (~)0.337s51.01x
🐘 PostgresExpress13.766s (+0.6%)14.016s (~)0.250s51.01x
🐘 PostgresNext.js (Turbopack)13.783s (~)14.219s (+1.4%)0.436s51.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express18.000s (+9.3% 🔺)20.026s (+9.4% 🔺)2.026s41.00x
▲ VercelNext.js (Turbopack)18.839s (+6.9% 🔺)20.609s (+6.5% 🔺)1.770s31.05x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express12.073s (-3.0%)12.875s (-1.2%)0.802s71.00x
💻 LocalNitro12.126s (+0.9%)13.026s (+3.0%)0.901s71.00x
💻 LocalNext.js (Turbopack)12.134s (-3.1%)13.025s (-1.1%)0.892s71.01x
💻 LocalExpress12.203s (+1.6%)13.027s (+6.1% 🔺)0.824s71.01x
🐘 PostgresNext.js (Turbopack)12.229s (-2.2%)13.017s (~)0.788s71.01x
🐘 PostgresNitro12.302s (-0.9%)13.022s (~)0.720s71.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express19.662s (+7.8% 🔺)21.742s (+8.4% 🔺)2.080s51.00x
▲ VercelNext.js (Turbopack)21.512s (+1.3%)23.389s (+1.6%)1.877s41.09x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.166s (-0.8%)2.008s (~)0.842s151.00x
🐘 PostgresNitro1.182s (-0.6%)2.008s (~)0.825s151.01x
🐘 PostgresNext.js (Turbopack)1.191s (-1.6%)2.007s (-3.3%)0.816s151.02x
💻 LocalNitro1.400s (+1.8%)2.006s (~)0.606s151.20x
💻 LocalExpress1.421s (+1.9%)2.007s (~)0.586s151.22x
💻 LocalNext.js (Turbopack)1.472s (+4.7%)2.007s (~)0.535s151.26x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.561s (+18.3% 🔺)4.068s (+5.3% 🔺)1.508s81.00x
▲ VercelNext.js (Turbopack)4.041s (+25.4% 🔺)5.752s (+17.5% 🔺)1.711s61.58x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.300s (-2.4%)3.008s (~)1.708s101.00x
🐘 PostgresExpress1.331s (-1.3%)2.317s (-7.7% 🟢)0.985s131.02x
🐘 PostgresNitro1.354s (+2.3%)2.394s (-12.5% 🟢)1.040s131.04x
💻 LocalNitro2.447s (-0.7%)2.918s (-3.0%)0.470s111.88x
💻 LocalExpress2.781s (+12.0% 🔺)3.109s (+3.3%)0.328s102.14x
💻 LocalNext.js (Turbopack)2.807s (+16.9% 🔺)3.109s (+3.3%)0.302s102.16x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.787s (+10.9% 🔺)4.403s (+8.4% 🔺)1.616s71.00x
▲ VercelNext.js (Turbopack)3.872s (+8.7% 🔺)6.078s (+16.8% 🔺)2.207s51.39x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.612s (~)4.012s (-5.9% 🟢)2.399s81.00x
🐘 PostgresNitro1.670s (~)4.441s (-6.1% 🟢)2.770s71.04x
🐘 PostgresNext.js (Turbopack)2.288s (-29.4% 🟢)5.182s (-13.9% 🟢)2.894s61.42x
💻 LocalNitro6.144s (-1.0%)6.816s (-2.8%)0.672s53.81x
💻 LocalExpress6.790s (+13.7% 🔺)7.416s (+15.6% 🔺)0.626s54.21x
💻 LocalNext.js (Turbopack)6.878s (+15.4% 🔺)8.268s (+14.6% 🔺)1.391s44.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express4.610s (+67.5% 🔺)6.681s (+53.2% 🔺)2.070s51.00x
▲ VercelNext.js (Turbopack)5.407s (+22.7% 🔺)7.280s (+16.6% 🔺)1.873s51.17x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.177s (~)2.008s (~)0.831s151.00x
🐘 PostgresNitro1.189s (+0.6%)2.008s (~)0.819s151.01x
🐘 PostgresNext.js (Turbopack)1.195s (~)2.006s (~)0.812s151.02x
💻 LocalNitro1.415s (+1.2%)2.007s (~)0.592s151.20x
💻 LocalExpress1.416s (+1.4%)2.007s (~)0.590s151.20x
💻 LocalNext.js (Turbopack)1.443s (~)2.006s (~)0.563s151.23x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.246s (+8.3% 🔺)3.921s (+6.6% 🔺)1.674s81.00x
▲ VercelNext.js (Turbopack)3.820s (+19.2% 🔺)5.341s (+10.0% 🔺)1.521s61.70x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.291s (-3.8%)2.316s (+0.9%)1.025s131.00x
🐘 PostgresNext.js (Turbopack)1.294s (-1.6%)3.010s (~)1.716s101.00x
🐘 PostgresNitro1.347s (+4.2%)2.317s (~)0.970s131.04x
💻 LocalNitro2.539s (+5.3% 🔺)3.009s (~)0.470s101.97x
💻 LocalExpress2.694s (+11.9% 🔺)3.009s (~)0.315s102.09x
💻 LocalNext.js (Turbopack)2.710s (+2.7%)3.010s (-3.2%)0.300s102.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.881s (+16.6% 🔺)4.624s (+20.8% 🔺)1.743s71.00x
▲ VercelNext.js (Turbopack)4.760s (+39.8% 🔺)6.645s (+38.4% 🔺)1.886s51.65x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.637s (+0.5%)4.447s (+10.9% 🔺)2.811s71.00x
🐘 PostgresNitro1.879s (+14.0% 🔺)4.870s (+17.6% 🔺)2.991s71.15x
🐘 PostgresNext.js (Turbopack)3.326s (+4.1%)6.215s (~)2.889s52.03x
💻 LocalNext.js (Turbopack)6.042s (+7.0% 🔺)6.618s (+6.5% 🔺)0.575s53.69x
💻 LocalExpress6.361s (+14.7% 🔺)7.017s (+12.9% 🔺)0.655s53.89x
💻 LocalNitro7.257s (+22.0% 🔺)8.020s (+24.9% 🔺)0.763s44.43x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.768s (+38.5% 🔺)5.671s (+29.3% 🔺)1.903s61.00x
▲ VercelNext.js (Turbopack)4.935s (-8.6% 🟢)6.674s (-6.1% 🟢)1.738s51.31x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.511s (-17.2% 🟢)1.006s (-1.9%)0.495s601.00x
🐘 PostgresExpress0.551s (-5.4% 🟢)1.023s (-1.8%)0.472s591.08x
🐘 PostgresNitro0.584s (+1.0%)1.006s (-1.6%)0.422s601.14x
💻 LocalNitro0.585s (~)1.022s (~)0.437s591.15x
💻 LocalExpress0.590s (+5.8% 🔺)1.005s (~)0.416s601.15x
💻 LocalNext.js (Turbopack)0.611s (+2.0%)1.022s (+1.7%)0.411s591.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.182s (+29.4% 🔺)5.219s (+35.4% 🔺)2.037s121.00x
▲ VercelNext.js (Turbopack)4.964s (+32.2% 🔺)6.751s (+30.3% 🔺)1.786s91.56x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.229s (-11.6% 🟢)2.007s (~)0.778s451.00x
🐘 PostgresExpress1.345s (-3.1%)2.053s (~)0.708s441.09x
🐘 PostgresNitro1.412s (+7.7% 🔺)2.054s (+2.3%)0.642s441.15x
💻 LocalNitro1.478s (+3.6%)2.006s (~)0.528s451.20x
💻 LocalExpress1.492s (+3.0%)2.006s (-1.1%)0.514s451.21x
💻 LocalNext.js (Turbopack)1.498s (~)2.006s (~)0.508s451.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express8.097s (+38.5% 🔺)10.089s (+35.8% 🔺)1.992s91.00x
▲ VercelNext.js (Turbopack)9.498s (+12.5% 🔺)11.514s (+13.2% 🔺)2.017s81.17x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)2.413s (-13.6% 🟢)3.008s (-3.4%)0.594s401.00x
🐘 PostgresExpress2.517s (-7.9% 🟢)3.034s (-1.7%)0.517s401.04x
🐘 PostgresNitro2.726s (+0.7%)3.086s (-1.6%)0.360s391.13x
💻 LocalNitro3.138s (-2.1%)3.913s (+2.4%)0.775s311.30x
💻 LocalNext.js (Turbopack)3.225s (-1.7%)3.978s (-0.8%)0.753s311.34x
💻 LocalExpress3.300s (+4.4%)4.010s (+5.8% 🔺)0.710s301.37x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express18.146s (+52.1% 🔺)20.493s (+46.9% 🔺)2.347s71.00x
▲ VercelNext.js (Turbopack)22.977s (+21.7% 🔺)24.987s (+19.6% 🔺)2.011s51.27x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.191s (+1.6%)1.023s (+1.5%)0.832s591.00x
🐘 PostgresExpress0.201s (-9.8% 🟢)1.006s (~)0.805s601.05x
🐘 PostgresNitro0.214s (-1.8%)1.006s (~)0.792s601.12x
💻 LocalNitro0.462s (+7.7% 🔺)1.005s (~)0.543s602.41x
💻 LocalExpress0.483s (+13.1% 🔺)1.005s (~)0.522s602.52x
💻 LocalNext.js (Turbopack)0.628s (+6.0% 🔺)1.022s (+1.7%)0.394s593.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.370s (+19.6% 🔺)2.945s (+15.5% 🔺)1.575s211.00x
▲ VercelNext.js (Turbopack)2.815s (+36.1% 🔺)4.511s (+21.4% 🔺)1.696s142.05x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.270s (-9.4% 🟢)1.006s (-1.2%)0.736s901.00x
🐘 PostgresExpress0.318s (-5.9% 🟢)1.006s (-1.1%)0.688s901.18x
🐘 PostgresNitro0.345s (+1.2%)1.007s (~)0.662s901.28x
💻 LocalNitro2.079s (-4.1%)2.686s (-4.0%)0.607s347.71x
💻 LocalExpress2.164s (+2.0%)2.767s (+1.1%)0.603s338.02x
💻 LocalNext.js (Turbopack)2.792s (+7.0% 🔺)3.258s (+4.6%)0.466s2810.35x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.990s (+36.3% 🔺)3.997s (+30.3% 🔺)2.006s231.00x
▲ VercelNext.js (Turbopack)3.585s (+23.3% 🔺)5.408s (+12.6% 🔺)1.823s171.80x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.478s (-9.6% 🟢)3.010s (-1.7%)2.532s401.00x
🐘 PostgresExpress0.532s (-2.6%)1.149s (+3.7%)0.618s1051.11x
🐘 PostgresNitro0.545s (-3.0%)1.050s (-3.5%)0.505s1151.14x
💻 LocalNitro10.004s (+6.8% 🔺)11.031s (+5.6% 🔺)1.027s1120.93x
💻 LocalNext.js (Turbopack)10.057s (+6.1% 🔺)11.211s (+6.5% 🔺)1.155s1121.04x
💻 LocalExpress10.417s (+13.4% 🔺)11.305s (+9.1% 🔺)0.887s1121.79x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.916s (+52.4% 🔺)4.704s (+22.2% 🔺)1.788s261.00x
▲ VercelNext.js (Turbopack)5.493s (+46.5% 🔺)7.471s (+35.5% 🔺)1.979s171.88x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.137s (-1.3%)1.969s (~)0.012s (+17.0% 🔺)2.020s (~)0.883s101.00x
🐘 PostgresExpress1.145s (-1.1%)2.000s (~)0.001s (-21.4% 🟢)2.010s (~)0.865s101.01x
🐘 PostgresNext.js (Turbopack)1.153s (-1.0%)2.000s (~)0.001s (+30.0% 🔺)2.010s (~)0.857s101.01x
💻 LocalNitro1.160s (+0.8%)2.004s (~)0.012s (+9.7% 🔺)2.020s (~)0.859s101.02x
💻 LocalExpress1.164s (+1.1%)2.005s (~)0.012s (+22.8% 🔺)2.021s (~)0.856s101.02x
🐘 PostgresNitro1.166s (-2.1%)1.999s (~)0.001s (-26.7% 🟢)2.011s (~)0.845s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.974s (-7.9% 🟢)3.569s (+9.0% 🔺)2.038s (+21.1% 🔺)6.129s (+12.0% 🔺)4.155s101.00x
▲ VercelNext.js (Turbopack)3.986s (+28.8% 🔺)4.016s (+6.1% 🔺)1.953s (+42.3% 🔺)7.748s (+23.1% 🔺)3.761s102.02x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.540s (-0.8%)2.003s (~)0.005s (+4.7%)2.026s (~)0.486s301.00x
💻 LocalNitro1.547s (+0.6%)2.009s (~)0.013s (+3.7%)2.025s (~)0.479s301.00x
🐘 PostgresNext.js (Turbopack)1.558s (-3.7%)2.009s (~)0.005s (+2.6%)2.026s (~)0.467s301.01x
💻 LocalExpress1.574s (+2.7%)2.011s (~)0.013s (+0.8%)2.026s (~)0.453s301.02x
💻 LocalNext.js (Turbopack)1.584s (~)1.971s (~)0.011s (-23.6% 🟢)2.024s (~)0.441s301.03x
🐘 PostgresNitro1.597s (+0.7%)2.002s (~)0.005s (+3.4%)2.026s (~)0.428s301.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express6.367s (+16.0% 🔺)7.885s (+13.5% 🔺)0.250s (+8.7% 🔺)8.629s (+11.5% 🔺)2.261s71.00x
▲ VercelNext.js (Turbopack)10.932s (+18.7% 🔺)11.820s (+8.5% 🔺)0.243s (-3.3%)13.252s (+14.1% 🔺)2.320s51.72x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.769s (-6.4% 🟢)1.065s (~)0.000s (-32.1% 🟢)1.102s (+2.2%)0.333s551.00x
🐘 PostgresNitro0.806s (-1.8%)1.045s (-5.2% 🟢)0.000s (-36.8% 🟢)1.060s (-5.4% 🟢)0.253s571.05x
🐘 PostgresNext.js (Turbopack)1.006s (+0.8%)1.418s (-2.9%)0.000s (-100.0% 🟢)1.429s (-3.7%)0.422s431.31x
💻 LocalExpress1.240s (+0.6%)2.013s (~)0.000s (-60.0% 🟢)2.015s (~)0.776s301.61x
💻 LocalNitro1.268s (~)2.013s (~)0.000s (+14.3% 🔺)2.016s (~)0.748s301.65x
💻 LocalNext.js (Turbopack)1.354s (-2.9%)1.979s (~)0.001s (+77.8% 🔺)2.016s (~)0.662s301.76x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.701s (+27.1% 🔺)5.023s (+15.1% 🔺)0.002s (+Infinity% 🔺)5.526s (+14.0% 🔺)1.825s111.00x
▲ VercelNext.js (Turbopack)5.168s (+6.3% 🔺)6.181s (+10.6% 🔺)0.000s (NaN%)6.952s (+1.2%)1.784s91.40x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.703s (+0.7%)2.301s (~)0.000s (+Infinity% 🔺)2.316s (~)0.613s261.00x
🐘 PostgresNitro1.770s (-3.3%)2.301s (-3.2%)0.000s (-100.0% 🟢)2.332s (-3.0%)0.562s261.04x
🐘 PostgresNext.js (Turbopack)2.551s (-17.5% 🟢)3.051s (-13.6% 🟢)0.000s (-71.7% 🟢)3.059s (-13.6% 🟢)0.508s201.50x
💻 LocalNitro3.469s (+5.1% 🔺)4.095s (+1.7%)0.000s (-30.0% 🟢)4.099s (+1.7%)0.630s152.04x
💻 LocalExpress3.538s (+7.8% 🔺)4.027s (+4.9%)0.001s (+113.3% 🔺)4.030s (+4.9%)0.492s152.08x
💻 LocalNext.js (Turbopack)3.541s (~)4.058s (+1.7%)0.001s (+113.3% 🔺)4.098s (+1.4%)0.557s152.08x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.371s (+2.8%)6.997s (+7.5% 🔺)0.000s (-100.0% 🟢)7.482s (+7.0% 🔺)2.111s91.00x
▲ VercelNext.js (Turbopack)8.546s (+24.9% 🔺)8.850s (+15.0% 🔺)0.000s (-100.0% 🟢)10.343s (+19.1% 🔺)1.797s61.59x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro16/21
🐘 PostgresNext.js (Turbopack)10/21
▲ VercelExpress21/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres17/21
Next.js (Turbopack)🐘 Postgres17/21
Nitro🐘 Postgres14/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)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

Comment thread.changeset/changelog.mjs Outdated
…tten summary verbatim, leaking injected `pr:`/`commit:` directive lines into the changelog so the entry title becomes literally `pr: #N`.
This commit fixes the issue reported at .changeset/changelog.mjs:368
## Bug
`maybeRewriteChangesetForBackport` prepends directive lines to a backport changeset's summary:
```js
const rewrittenSummary =
`pr: #${originalPRNumber}\n` +
`commit: ${changeset.commit}\n` +
summary;
return { ...changeset, summary: rewrittenSummary };
```
The upstream `@changesets/changelog-github` `getReleaseLine` consumes those directives and **strips them** from the summary (via `.replace(/^\s*(?:pr|pull|pull\s+request):.../im, ...)`, `commit:`, `author:` etc.) before splitting into `firstLine`/`futureLines` and rendering. So in the normal path the directives never appear in the output — that's the whole point of injecting them.
The offline fallback, however, does **not** strip them:
```js
function offlineReleaseLine(changeset, options) {
const [firstLine, ...futureLines] = (changeset.summary ?? '')
.split('\n')
.map((l) => l.trimEnd());
...
}
```
It just splits on `\n` and renders verbatim.
### Concrete trigger
A backport changeset where:
1. `maybeRewriteChangesetForBackport` **succeeds** (network up, PR resolved, directives injected → `rewritten.summary` starts with `pr: #N\ncommit: <sha>\n`), and
2. the subsequent `limitUpstream(() => upstream.getReleaseLine(rewritten, ...))` **throws** — exactly the flaky/oversized GraphQL batch scenario this fallback was added for.
The catch block then ran `offlineReleaseLine(rewritten, options)`, producing:
```
- [`abc1234`](...) - pr: #123
commit: abc1234...
<real first line>
```
The entry title becomes literally `pr: #123` and `commit: …` leaks as body text.
## Fix
Pass the **original** `changeset` to `offlineReleaseLine` instead of `rewritten`. The backport rewrite only mutates `summary`; `changeset.commit` is untouched, so the offline commit link is identical, while the original summary has no injected directives. The offline path already drops PR/author attribution it can't resolve, so using the un-rewritten summary is exactly correct.
The dependency-line path (`offlineDependencyReleaseLine`) is unaffected since no directive injection happens there.
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: VaguelySerious <mittgfu@gmail.com>

@pranaygppranaygp 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.

Reviewed the diff and verified the core mechanism end to end against the real file (stubbed upstream, Node 22.18, require()-able ESM confirmed):

  • Limiter works as claimed: 120 getReleaseLine calls → max 25 concurrent → ~5 DataLoader batches of ≤25, order preserved. Since get-github-info's DataLoader is a module-level singleton built with no maxBatchSize (confirmed in get-github-info@0.7.0 src) and each getReleaseLine fires exactly one .load(), gating concurrency at 25 genuinely bounds the per-batch query size while preserving full PR/author attribution. Good, well-reasoned fix.
  • Offline getReleaseLine mirrors the upstream template exactly (changelog-github@0.5.2): `\n\n-${prefix ? ` ${prefix} -` : ''} ${firstLine}...`, dropping only the pull/Thanks attribution. The 2nd-commit fix (feed the original changeset, not rewritten) is correct and I confirmed it no longer leaks pr:/commit: directives.
  • Offline dependency line is lossless — dep lines only ever render commit links, which you reconstruct from the SHA, so that path loses nothing even when it falls back.

LGTM to ship — it unblocks the release. A few non-blocking notes inline (one substantive: the dependency-rollup path isn't actually batch-bounded by the limiter and is carried entirely by the fallback).

// truncated/gzip-broken body (which `node-fetch@2` surfaces as
// `ERR_STREAM_PREMATURE_CLOSE`) or a 502, aborting the whole release.
// 6. Wraps the upstream delegation in a try/catch with a network-free
// fallback line, so a transient failure on one batch degrades a single

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.

Minor wording: "degrades a single entry" undersells it. When an oversized getReleaseLine batch fails, every .load() sharing that one fetch rejects together, so a whole batch (~UPSTREAM_CONCURRENCY, up to 25 entries) drops to the offline line at once — not one. The important half ("instead of failing the release") still holds; just the count is off.

options,
);
try {
return await limitUpstream(() =>

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.

The concurrency gate bounds the getReleaseLine batch nicely, but it does not bound this path's batch. apply-release-plan's getChangelogEntry calls getDependencyReleaseLine once per release with relevantChangesets (every changeset that bumped that package's deps), and upstream fans out to one getInfo() per changeset inside a single internal Promise.all — so all of those .load()s land in one DataLoader batch within this one limiter slot. A package whose deps were bumped by hundreds of changesets (e.g. the many packages depending on @workflow/core) therefore still produces one oversized query here, exactly the shape this PR is fixing.

In practice the release stays safe: that query throws → catchofflineDependencyReleaseLine, which is lossless for dependency lines (they only render commit links). So no functional gap. But point 5's rationale — "splits its lookups across several small GraphQL queries" — only really applies to getReleaseLine; this path is carried by the fallback, not by batch-splitting. Worth a word in the comments so the next reader doesn't assume both paths are bounded (or, if you want it truly bounded, gate the per-getInfo fan-out rather than the outer call).

// the changelog (the entry title would become literally `pr: #N`). The
// rewrite only touches `summary`; `changeset.commit` is unchanged, so the
// offline commit link is identical either way.
return offlineReleaseLine(changeset, options);

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.

Good fix on the backport-rewrite leak. One residual of the same class: if a changeset's summary manually contains a pr:/commit:/author: directive, maybeRewriteChangesetForBackport returns it unchanged (the guard at L309), upstream would strip it before rendering — but the offline path renders it verbatim, so the entry title becomes literally pr: #123 (confirmed locally). Much rarer than the backport case (needs a hand-written directive and an upstream failure), so not a blocker. If you want the offline path to fully match upstream, strip the leading directive lines here the same way upstream's .replace(/^\s*(?:pr|pull|...|author|user):/im, '') chain does.

: ` \`${short}\``;
}

function offlineReleaseLine(changeset, options) {

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.

Optional: these offline helpers + the limiter are pure and easy to unit-test, and the directive leak already regressed once (needed a follow-up commit), which is a good signal this file is edit-fragile. A tiny test — force upstream to throw and assert the offline line keeps the commit link + a clean title, plus assert 120 calls never exceed 25 concurrent — would be cheap regression insurance. No test runner is wired for .changeset/ today, so low priority, but worth considering.

@VaguelySerious
VaguelySerious merged commit 7f04d87 into mainJun 26, 2026
112 of 113 checks passed
@VaguelySerious
VaguelySerious deleted the peter/fix-release-changelog-premature-close branch June 26, 2026 21:42
github-actionsBot added a commit that referenced this pull request Jun 26, 2026
…h size (#2669)
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #2671. (backport job run)

VaguelySerious added a commit that referenced this pull request Jun 26, 2026
…h size (#2669) (#2671)
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
pranaygp added a commit that referenced this pull request Jun 26, 2026
…edrive
* origin/main: (90 commits)
[ai] Fix "missing text part" stream crash by repairing UI message framing (#2537)
[ci] Fix release "Premature close" by bounding changelog GraphQL batch size (#2669)
[ai] Drop orphan UI chunks after negative-index reconnect (#2082)
Fix trace detail panel Input/Output flicker (selection-driven state machine) (#2637)
[world-local] Scope untagged startup recovery to untagged runs (#2667)
fix(world-postgres): defer loopback worker startup (#2657)
docs: deprecate DurableAgent and WorkflowChatTransport, point to AI SDK WorkflowAgent (#2616)
[web] Fix HTTP/2 in bundled server build so observability reads work (#2632)
[core] Fix turbo-mode step-body writes racing run_started (#2629)
fix(world-postgres): rename setup command (#2644)
chore: ignore workflow swc caches (#2640)
fix(ai): ignore intentional stream aborts (#2635)
fix(nitro): reload steps during Vite HMR (#2572)
[world] Make @workflow/world own its Zod dependency (#2622)
feat(web-shared): extend cn with custom tailwind-merge class groups (#2619)
[world-vercel] Enable HTTP/2 for the events API and stream writes (#2573)
Remove shadow from off-screen marker indicator button (#2614)
Fix discovery of dotted JS workflow imports (#2594)
Version Packages (beta) (#2597)
Thread occurredAt into web-shared entities (#2608)
...
# Conflicts:
#	packages/core/src/runtime/step-executor.ts
#	packages/world-vercel/src/http-client.ts
Sign up for freeto 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.

2 participants

@VaguelySerious@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [ci] Fix release "Premature close" by bounding changelog GraphQL batch size by VaguelySerious · Pull Request #2669 · vercel/workflow · GitHub
Skip to content

[ci] Fix release "Premature close" by bounding changelog GraphQL batch size - #2669

Merged
VaguelySerious merged 2 commits into
mainfrom
peter/fix-release-changelog-premature-close
Jun 26, 2026
Merged

[ci] Fix release "Premature close" by bounding changelog GraphQL batch size#2669
VaguelySerious merged 2 commits into
mainfrom
peter/fix-release-changelog-premature-close

Conversation

@VaguelySerious

Copy link
Copy Markdown
Member

Problem

The Release workflow's pnpm ci:version (changeset version) step has failed on the last ~10 runs (e.g. run 28264162169) with:

🦋 error FetchError: Invalid response body while trying to fetch https://api.github.com/graphql: Premature close
at Gunzip.<anonymous> (.../node-fetch@2.7.0/.../lib/index.js:217:52)
code: 'ERR_STREAM_PREMATURE_CLOSE'

Root cause

@changesets/changelog-github resolves each changelog entry via @changesets/get-github-info, whose DataLoader is constructed with no maxBatchSize. @changesets/apply-release-plan calls getReleaseLine for every changeset inside one Promise.all, so all commits fold into a single GraphQL query.

We currently have ~300 changesets accumulated (beta pre mode, not yet flushed). That one query grows past what GitHub's GraphQL endpoint will reliably serve. Reproduced against the live API with the exact query shape on Node 24 + node-fetch@2.7.0:

commits in one queryresult
50–290200 OK (~120KB gzip)
300flaky: 200 or broken body / invalid json "<html>…"
350502 Bad Gateway

GitHub returns a truncated/gzip-broken body — which node-fetch@2 surfaces as ERR_STREAM_PREMATURE_CLOSE — or a 502, aborting the whole release. It's deterministic now because the backlog is reliably past the threshold, and it only grows.

The custom .changeset/changelog.mjs wrapper already intends to "never want a flaky network call to break pnpm changeset version", but only guarded its own backport lookups — not the upstream.getReleaseLine delegation that runs node-fetch.

Fix

  1. Bound the batch size via a concurrency gate (25). Limiting how many upstream calls are in flight bounds the number of get-github-info.load() calls per tick, which bounds the DataLoader batch — so the work splits into several small queries GitHub serves reliably, preserving full PR/author attribution.
  2. Wrap the upstream delegation in try/catch with a network-free fallback line, so a transient failure on a single batch degrades one entry instead of failing the release (finally extends the wrapper's stated promise to the delegation path).

Validation (empirical, against the live GitHub API)

  • Ungated → 1 request of 120; gated p-limit(25)5 requests, max batch 25.
  • Through the real modified wrapper over 120 commits: 5 requests, max batch 25, all 120 lines retain their [\sha`](…)commit link +Thanks @…` attribution.
  • Forcing node-fetch to throw ERR_STREAM_PREMATURE_CLOSE: no throw, well-formed offline line with commit link + multi-line summary.

🤖 Generated with Claude Code

…h size
The Release workflow's `changeset version` step has failed on the last ~10
runs with `FetchError: ... api.github.com/graphql: Premature close`.
`@changesets/changelog-github` resolves each entry via
`@changesets/get-github-info`, whose DataLoader is created with no
`maxBatchSize`. `apply-release-plan` calls `getReleaseLine` for every
changeset in one `Promise.all`, so all commits fold into a single GraphQL
query. With ~300 changesets accumulated in beta pre mode, that query grows
past what GitHub's GraphQL endpoint reliably serves — it returns a
truncated/gzip-broken body (which node-fetch@2 surfaces as
ERR_STREAM_PREMATURE_CLOSE) or a 502, aborting the whole release.
The custom `.changeset/changelog.mjs` wrapper already aimed to never let a
network call break the release, but only guarded its own backport lookups,
not the upstream delegation that runs node-fetch.
Fix:
- Cap concurrent upstream calls (25) so the DataLoader batches split across
several small queries GitHub can serve, preserving full PR/author
attribution. Verified against the live API: 120 commits -> 5 requests of
<=25 instead of one batch of 120.
- Wrap the upstream delegation in try/catch with a network-free fallback
line, so a transient failure on one batch degrades one entry instead of
failing the release.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code ownersJune 26, 2026 21:09
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fcd41ca

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

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

vercelBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production144202301672
✅ 💻 Local Development160502191824
✅ 📦 Local Production160502191824
✅ 🐘 Local Postgres159302311824
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total7282010788360

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-turbopack14903
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable15101
✅ nitro-stable126026
✅ nuxt-stable126026
✅ sveltekit-stable14507
✅ vite-stable126026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable127025
✅ e2e-local-dev-tanstack-start-127025
✅ e2e-local-postgres-nest-stable126026
✅ e2e-local-postgres-tanstack-start-126026
✅ e2e-local-prod-nest-stable127025
✅ e2e-local-prod-tanstack-start-127025
✅ e2e-vercel-prod-tanstack-start125027

📋 View full workflow run

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.048s (+3.7%)1.006s (~)0.958s101.00x
💻 LocalNitro0.048s (+8.5% 🔺)1.006s (~)0.958s101.00x
💻 LocalNext.js (Turbopack)0.049s (~)1.006s (~)0.956s101.03x
🐘 PostgresNext.js (Turbopack)0.055s (-9.1% 🟢)1.011s (~)0.957s101.14x
🐘 PostgresExpress0.065s (-13.6% 🟢)1.012s (~)0.947s101.35x
🐘 PostgresNitro0.072s (~)1.013s (~)0.941s101.49x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.204s (-27.8% 🟢)1.652s (-13.6% 🟢)1.448s101.00x
▲ VercelNext.js (Turbopack)0.548s (-10.5% 🟢)2.528s (~)1.980s102.69x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.082s (~)2.006s (~)0.924s101.00x
💻 LocalExpress1.083s (+0.7%)2.007s (~)0.924s101.00x
🐘 PostgresNext.js (Turbopack)1.085s (-0.9%)2.010s (~)0.926s101.00x
💻 LocalNext.js (Turbopack)1.085s (+0.5%)2.006s (~)0.921s101.00x
🐘 PostgresExpress1.095s (~)2.010s (~)0.915s101.01x
🐘 PostgresNitro1.102s (+0.5%)2.010s (~)0.908s101.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.393s (+2.1%)3.035s (+0.9%)1.641s101.00x
▲ VercelNext.js (Turbopack)2.053s (-12.7% 🟢)3.736s (-0.7%)1.682s101.47x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.446s (~)11.022s (~)0.576s31.00x
💻 LocalExpress10.459s (~)11.024s (~)0.565s31.00x
🐘 PostgresNext.js (Turbopack)10.465s (-0.6%)11.015s (~)0.549s31.00x
💻 LocalNext.js (Turbopack)10.469s (~)11.023s (~)0.554s31.00x
🐘 PostgresNitro10.492s (~)11.018s (~)0.526s31.00x
🐘 PostgresExpress10.502s (~)11.020s (~)0.518s31.01x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express12.061s (+2.7%)14.083s (+4.4%)2.022s31.00x
▲ VercelNext.js (Turbopack)13.273s (+4.5%)15.530s (+5.7% 🔺)2.258s21.10x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)13.578s (~)14.027s (~)0.449s51.00x
💻 LocalNitro13.613s (~)14.027s (~)0.414s51.00x
💻 LocalExpress13.631s (+0.5%)14.027s (~)0.397s51.00x
🐘 PostgresNitro13.683s (~)14.020s (~)0.337s51.01x
🐘 PostgresExpress13.766s (+0.6%)14.016s (~)0.250s51.01x
🐘 PostgresNext.js (Turbopack)13.783s (~)14.219s (+1.4%)0.436s51.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express18.000s (+9.3% 🔺)20.026s (+9.4% 🔺)2.026s41.00x
▲ VercelNext.js (Turbopack)18.839s (+6.9% 🔺)20.609s (+6.5% 🔺)1.770s31.05x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express12.073s (-3.0%)12.875s (-1.2%)0.802s71.00x
💻 LocalNitro12.126s (+0.9%)13.026s (+3.0%)0.901s71.00x
💻 LocalNext.js (Turbopack)12.134s (-3.1%)13.025s (-1.1%)0.892s71.01x
💻 LocalExpress12.203s (+1.6%)13.027s (+6.1% 🔺)0.824s71.01x
🐘 PostgresNext.js (Turbopack)12.229s (-2.2%)13.017s (~)0.788s71.01x
🐘 PostgresNitro12.302s (-0.9%)13.022s (~)0.720s71.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express19.662s (+7.8% 🔺)21.742s (+8.4% 🔺)2.080s51.00x
▲ VercelNext.js (Turbopack)21.512s (+1.3%)23.389s (+1.6%)1.877s41.09x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.166s (-0.8%)2.008s (~)0.842s151.00x
🐘 PostgresNitro1.182s (-0.6%)2.008s (~)0.825s151.01x
🐘 PostgresNext.js (Turbopack)1.191s (-1.6%)2.007s (-3.3%)0.816s151.02x
💻 LocalNitro1.400s (+1.8%)2.006s (~)0.606s151.20x
💻 LocalExpress1.421s (+1.9%)2.007s (~)0.586s151.22x
💻 LocalNext.js (Turbopack)1.472s (+4.7%)2.007s (~)0.535s151.26x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.561s (+18.3% 🔺)4.068s (+5.3% 🔺)1.508s81.00x
▲ VercelNext.js (Turbopack)4.041s (+25.4% 🔺)5.752s (+17.5% 🔺)1.711s61.58x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.300s (-2.4%)3.008s (~)1.708s101.00x
🐘 PostgresExpress1.331s (-1.3%)2.317s (-7.7% 🟢)0.985s131.02x
🐘 PostgresNitro1.354s (+2.3%)2.394s (-12.5% 🟢)1.040s131.04x
💻 LocalNitro2.447s (-0.7%)2.918s (-3.0%)0.470s111.88x
💻 LocalExpress2.781s (+12.0% 🔺)3.109s (+3.3%)0.328s102.14x
💻 LocalNext.js (Turbopack)2.807s (+16.9% 🔺)3.109s (+3.3%)0.302s102.16x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.787s (+10.9% 🔺)4.403s (+8.4% 🔺)1.616s71.00x
▲ VercelNext.js (Turbopack)3.872s (+8.7% 🔺)6.078s (+16.8% 🔺)2.207s51.39x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.612s (~)4.012s (-5.9% 🟢)2.399s81.00x
🐘 PostgresNitro1.670s (~)4.441s (-6.1% 🟢)2.770s71.04x
🐘 PostgresNext.js (Turbopack)2.288s (-29.4% 🟢)5.182s (-13.9% 🟢)2.894s61.42x
💻 LocalNitro6.144s (-1.0%)6.816s (-2.8%)0.672s53.81x
💻 LocalExpress6.790s (+13.7% 🔺)7.416s (+15.6% 🔺)0.626s54.21x
💻 LocalNext.js (Turbopack)6.878s (+15.4% 🔺)8.268s (+14.6% 🔺)1.391s44.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express4.610s (+67.5% 🔺)6.681s (+53.2% 🔺)2.070s51.00x
▲ VercelNext.js (Turbopack)5.407s (+22.7% 🔺)7.280s (+16.6% 🔺)1.873s51.17x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.177s (~)2.008s (~)0.831s151.00x
🐘 PostgresNitro1.189s (+0.6%)2.008s (~)0.819s151.01x
🐘 PostgresNext.js (Turbopack)1.195s (~)2.006s (~)0.812s151.02x
💻 LocalNitro1.415s (+1.2%)2.007s (~)0.592s151.20x
💻 LocalExpress1.416s (+1.4%)2.007s (~)0.590s151.20x
💻 LocalNext.js (Turbopack)1.443s (~)2.006s (~)0.563s151.23x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.246s (+8.3% 🔺)3.921s (+6.6% 🔺)1.674s81.00x
▲ VercelNext.js (Turbopack)3.820s (+19.2% 🔺)5.341s (+10.0% 🔺)1.521s61.70x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.291s (-3.8%)2.316s (+0.9%)1.025s131.00x
🐘 PostgresNext.js (Turbopack)1.294s (-1.6%)3.010s (~)1.716s101.00x
🐘 PostgresNitro1.347s (+4.2%)2.317s (~)0.970s131.04x
💻 LocalNitro2.539s (+5.3% 🔺)3.009s (~)0.470s101.97x
💻 LocalExpress2.694s (+11.9% 🔺)3.009s (~)0.315s102.09x
💻 LocalNext.js (Turbopack)2.710s (+2.7%)3.010s (-3.2%)0.300s102.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.881s (+16.6% 🔺)4.624s (+20.8% 🔺)1.743s71.00x
▲ VercelNext.js (Turbopack)4.760s (+39.8% 🔺)6.645s (+38.4% 🔺)1.886s51.65x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.637s (+0.5%)4.447s (+10.9% 🔺)2.811s71.00x
🐘 PostgresNitro1.879s (+14.0% 🔺)4.870s (+17.6% 🔺)2.991s71.15x
🐘 PostgresNext.js (Turbopack)3.326s (+4.1%)6.215s (~)2.889s52.03x
💻 LocalNext.js (Turbopack)6.042s (+7.0% 🔺)6.618s (+6.5% 🔺)0.575s53.69x
💻 LocalExpress6.361s (+14.7% 🔺)7.017s (+12.9% 🔺)0.655s53.89x
💻 LocalNitro7.257s (+22.0% 🔺)8.020s (+24.9% 🔺)0.763s44.43x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.768s (+38.5% 🔺)5.671s (+29.3% 🔺)1.903s61.00x
▲ VercelNext.js (Turbopack)4.935s (-8.6% 🟢)6.674s (-6.1% 🟢)1.738s51.31x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.511s (-17.2% 🟢)1.006s (-1.9%)0.495s601.00x
🐘 PostgresExpress0.551s (-5.4% 🟢)1.023s (-1.8%)0.472s591.08x
🐘 PostgresNitro0.584s (+1.0%)1.006s (-1.6%)0.422s601.14x
💻 LocalNitro0.585s (~)1.022s (~)0.437s591.15x
💻 LocalExpress0.590s (+5.8% 🔺)1.005s (~)0.416s601.15x
💻 LocalNext.js (Turbopack)0.611s (+2.0%)1.022s (+1.7%)0.411s591.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.182s (+29.4% 🔺)5.219s (+35.4% 🔺)2.037s121.00x
▲ VercelNext.js (Turbopack)4.964s (+32.2% 🔺)6.751s (+30.3% 🔺)1.786s91.56x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.229s (-11.6% 🟢)2.007s (~)0.778s451.00x
🐘 PostgresExpress1.345s (-3.1%)2.053s (~)0.708s441.09x
🐘 PostgresNitro1.412s (+7.7% 🔺)2.054s (+2.3%)0.642s441.15x
💻 LocalNitro1.478s (+3.6%)2.006s (~)0.528s451.20x
💻 LocalExpress1.492s (+3.0%)2.006s (-1.1%)0.514s451.21x
💻 LocalNext.js (Turbopack)1.498s (~)2.006s (~)0.508s451.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express8.097s (+38.5% 🔺)10.089s (+35.8% 🔺)1.992s91.00x
▲ VercelNext.js (Turbopack)9.498s (+12.5% 🔺)11.514s (+13.2% 🔺)2.017s81.17x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)2.413s (-13.6% 🟢)3.008s (-3.4%)0.594s401.00x
🐘 PostgresExpress2.517s (-7.9% 🟢)3.034s (-1.7%)0.517s401.04x
🐘 PostgresNitro2.726s (+0.7%)3.086s (-1.6%)0.360s391.13x
💻 LocalNitro3.138s (-2.1%)3.913s (+2.4%)0.775s311.30x
💻 LocalNext.js (Turbopack)3.225s (-1.7%)3.978s (-0.8%)0.753s311.34x
💻 LocalExpress3.300s (+4.4%)4.010s (+5.8% 🔺)0.710s301.37x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express18.146s (+52.1% 🔺)20.493s (+46.9% 🔺)2.347s71.00x
▲ VercelNext.js (Turbopack)22.977s (+21.7% 🔺)24.987s (+19.6% 🔺)2.011s51.27x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.191s (+1.6%)1.023s (+1.5%)0.832s591.00x
🐘 PostgresExpress0.201s (-9.8% 🟢)1.006s (~)0.805s601.05x
🐘 PostgresNitro0.214s (-1.8%)1.006s (~)0.792s601.12x
💻 LocalNitro0.462s (+7.7% 🔺)1.005s (~)0.543s602.41x
💻 LocalExpress0.483s (+13.1% 🔺)1.005s (~)0.522s602.52x
💻 LocalNext.js (Turbopack)0.628s (+6.0% 🔺)1.022s (+1.7%)0.394s593.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.370s (+19.6% 🔺)2.945s (+15.5% 🔺)1.575s211.00x
▲ VercelNext.js (Turbopack)2.815s (+36.1% 🔺)4.511s (+21.4% 🔺)1.696s142.05x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.270s (-9.4% 🟢)1.006s (-1.2%)0.736s901.00x
🐘 PostgresExpress0.318s (-5.9% 🟢)1.006s (-1.1%)0.688s901.18x
🐘 PostgresNitro0.345s (+1.2%)1.007s (~)0.662s901.28x
💻 LocalNitro2.079s (-4.1%)2.686s (-4.0%)0.607s347.71x
💻 LocalExpress2.164s (+2.0%)2.767s (+1.1%)0.603s338.02x
💻 LocalNext.js (Turbopack)2.792s (+7.0% 🔺)3.258s (+4.6%)0.466s2810.35x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.990s (+36.3% 🔺)3.997s (+30.3% 🔺)2.006s231.00x
▲ VercelNext.js (Turbopack)3.585s (+23.3% 🔺)5.408s (+12.6% 🔺)1.823s171.80x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.478s (-9.6% 🟢)3.010s (-1.7%)2.532s401.00x
🐘 PostgresExpress0.532s (-2.6%)1.149s (+3.7%)0.618s1051.11x
🐘 PostgresNitro0.545s (-3.0%)1.050s (-3.5%)0.505s1151.14x
💻 LocalNitro10.004s (+6.8% 🔺)11.031s (+5.6% 🔺)1.027s1120.93x
💻 LocalNext.js (Turbopack)10.057s (+6.1% 🔺)11.211s (+6.5% 🔺)1.155s1121.04x
💻 LocalExpress10.417s (+13.4% 🔺)11.305s (+9.1% 🔺)0.887s1121.79x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.916s (+52.4% 🔺)4.704s (+22.2% 🔺)1.788s261.00x
▲ VercelNext.js (Turbopack)5.493s (+46.5% 🔺)7.471s (+35.5% 🔺)1.979s171.88x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.137s (-1.3%)1.969s (~)0.012s (+17.0% 🔺)2.020s (~)0.883s101.00x
🐘 PostgresExpress1.145s (-1.1%)2.000s (~)0.001s (-21.4% 🟢)2.010s (~)0.865s101.01x
🐘 PostgresNext.js (Turbopack)1.153s (-1.0%)2.000s (~)0.001s (+30.0% 🔺)2.010s (~)0.857s101.01x
💻 LocalNitro1.160s (+0.8%)2.004s (~)0.012s (+9.7% 🔺)2.020s (~)0.859s101.02x
💻 LocalExpress1.164s (+1.1%)2.005s (~)0.012s (+22.8% 🔺)2.021s (~)0.856s101.02x
🐘 PostgresNitro1.166s (-2.1%)1.999s (~)0.001s (-26.7% 🟢)2.011s (~)0.845s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.974s (-7.9% 🟢)3.569s (+9.0% 🔺)2.038s (+21.1% 🔺)6.129s (+12.0% 🔺)4.155s101.00x
▲ VercelNext.js (Turbopack)3.986s (+28.8% 🔺)4.016s (+6.1% 🔺)1.953s (+42.3% 🔺)7.748s (+23.1% 🔺)3.761s102.02x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.540s (-0.8%)2.003s (~)0.005s (+4.7%)2.026s (~)0.486s301.00x
💻 LocalNitro1.547s (+0.6%)2.009s (~)0.013s (+3.7%)2.025s (~)0.479s301.00x
🐘 PostgresNext.js (Turbopack)1.558s (-3.7%)2.009s (~)0.005s (+2.6%)2.026s (~)0.467s301.01x
💻 LocalExpress1.574s (+2.7%)2.011s (~)0.013s (+0.8%)2.026s (~)0.453s301.02x
💻 LocalNext.js (Turbopack)1.584s (~)1.971s (~)0.011s (-23.6% 🟢)2.024s (~)0.441s301.03x
🐘 PostgresNitro1.597s (+0.7%)2.002s (~)0.005s (+3.4%)2.026s (~)0.428s301.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express6.367s (+16.0% 🔺)7.885s (+13.5% 🔺)0.250s (+8.7% 🔺)8.629s (+11.5% 🔺)2.261s71.00x
▲ VercelNext.js (Turbopack)10.932s (+18.7% 🔺)11.820s (+8.5% 🔺)0.243s (-3.3%)13.252s (+14.1% 🔺)2.320s51.72x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.769s (-6.4% 🟢)1.065s (~)0.000s (-32.1% 🟢)1.102s (+2.2%)0.333s551.00x
🐘 PostgresNitro0.806s (-1.8%)1.045s (-5.2% 🟢)0.000s (-36.8% 🟢)1.060s (-5.4% 🟢)0.253s571.05x
🐘 PostgresNext.js (Turbopack)1.006s (+0.8%)1.418s (-2.9%)0.000s (-100.0% 🟢)1.429s (-3.7%)0.422s431.31x
💻 LocalExpress1.240s (+0.6%)2.013s (~)0.000s (-60.0% 🟢)2.015s (~)0.776s301.61x
💻 LocalNitro1.268s (~)2.013s (~)0.000s (+14.3% 🔺)2.016s (~)0.748s301.65x
💻 LocalNext.js (Turbopack)1.354s (-2.9%)1.979s (~)0.001s (+77.8% 🔺)2.016s (~)0.662s301.76x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.701s (+27.1% 🔺)5.023s (+15.1% 🔺)0.002s (+Infinity% 🔺)5.526s (+14.0% 🔺)1.825s111.00x
▲ VercelNext.js (Turbopack)5.168s (+6.3% 🔺)6.181s (+10.6% 🔺)0.000s (NaN%)6.952s (+1.2%)1.784s91.40x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.703s (+0.7%)2.301s (~)0.000s (+Infinity% 🔺)2.316s (~)0.613s261.00x
🐘 PostgresNitro1.770s (-3.3%)2.301s (-3.2%)0.000s (-100.0% 🟢)2.332s (-3.0%)0.562s261.04x
🐘 PostgresNext.js (Turbopack)2.551s (-17.5% 🟢)3.051s (-13.6% 🟢)0.000s (-71.7% 🟢)3.059s (-13.6% 🟢)0.508s201.50x
💻 LocalNitro3.469s (+5.1% 🔺)4.095s (+1.7%)0.000s (-30.0% 🟢)4.099s (+1.7%)0.630s152.04x
💻 LocalExpress3.538s (+7.8% 🔺)4.027s (+4.9%)0.001s (+113.3% 🔺)4.030s (+4.9%)0.492s152.08x
💻 LocalNext.js (Turbopack)3.541s (~)4.058s (+1.7%)0.001s (+113.3% 🔺)4.098s (+1.4%)0.557s152.08x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.371s (+2.8%)6.997s (+7.5% 🔺)0.000s (-100.0% 🟢)7.482s (+7.0% 🔺)2.111s91.00x
▲ VercelNext.js (Turbopack)8.546s (+24.9% 🔺)8.850s (+15.0% 🔺)0.000s (-100.0% 🟢)10.343s (+19.1% 🔺)1.797s61.59x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro16/21
🐘 PostgresNext.js (Turbopack)10/21
▲ VercelExpress21/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres17/21
Next.js (Turbopack)🐘 Postgres17/21
Nitro🐘 Postgres14/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)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

Comment thread.changeset/changelog.mjs Outdated
…tten summary verbatim, leaking injected `pr:`/`commit:` directive lines into the changelog so the entry title becomes literally `pr: #N`.
This commit fixes the issue reported at .changeset/changelog.mjs:368
## Bug
`maybeRewriteChangesetForBackport` prepends directive lines to a backport changeset's summary:
```js
const rewrittenSummary =
`pr: #${originalPRNumber}\n` +
`commit: ${changeset.commit}\n` +
summary;
return { ...changeset, summary: rewrittenSummary };
```
The upstream `@changesets/changelog-github` `getReleaseLine` consumes those directives and **strips them** from the summary (via `.replace(/^\s*(?:pr|pull|pull\s+request):.../im, ...)`, `commit:`, `author:` etc.) before splitting into `firstLine`/`futureLines` and rendering. So in the normal path the directives never appear in the output — that's the whole point of injecting them.
The offline fallback, however, does **not** strip them:
```js
function offlineReleaseLine(changeset, options) {
const [firstLine, ...futureLines] = (changeset.summary ?? '')
.split('\n')
.map((l) => l.trimEnd());
...
}
```
It just splits on `\n` and renders verbatim.
### Concrete trigger
A backport changeset where:
1. `maybeRewriteChangesetForBackport` **succeeds** (network up, PR resolved, directives injected → `rewritten.summary` starts with `pr: #N\ncommit: <sha>\n`), and
2. the subsequent `limitUpstream(() => upstream.getReleaseLine(rewritten, ...))` **throws** — exactly the flaky/oversized GraphQL batch scenario this fallback was added for.
The catch block then ran `offlineReleaseLine(rewritten, options)`, producing:
```
- [`abc1234`](...) - pr: #123
commit: abc1234...
<real first line>
```
The entry title becomes literally `pr: #123` and `commit: …` leaks as body text.
## Fix
Pass the **original** `changeset` to `offlineReleaseLine` instead of `rewritten`. The backport rewrite only mutates `summary`; `changeset.commit` is untouched, so the offline commit link is identical, while the original summary has no injected directives. The offline path already drops PR/author attribution it can't resolve, so using the un-rewritten summary is exactly correct.
The dependency-line path (`offlineDependencyReleaseLine`) is unaffected since no directive injection happens there.
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: VaguelySerious <mittgfu@gmail.com>

@pranaygppranaygp 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.

Reviewed the diff and verified the core mechanism end to end against the real file (stubbed upstream, Node 22.18, require()-able ESM confirmed):

  • Limiter works as claimed: 120 getReleaseLine calls → max 25 concurrent → ~5 DataLoader batches of ≤25, order preserved. Since get-github-info's DataLoader is a module-level singleton built with no maxBatchSize (confirmed in get-github-info@0.7.0 src) and each getReleaseLine fires exactly one .load(), gating concurrency at 25 genuinely bounds the per-batch query size while preserving full PR/author attribution. Good, well-reasoned fix.
  • Offline getReleaseLine mirrors the upstream template exactly (changelog-github@0.5.2): `\n\n-${prefix ? ` ${prefix} -` : ''} ${firstLine}...`, dropping only the pull/Thanks attribution. The 2nd-commit fix (feed the original changeset, not rewritten) is correct and I confirmed it no longer leaks pr:/commit: directives.
  • Offline dependency line is lossless — dep lines only ever render commit links, which you reconstruct from the SHA, so that path loses nothing even when it falls back.

LGTM to ship — it unblocks the release. A few non-blocking notes inline (one substantive: the dependency-rollup path isn't actually batch-bounded by the limiter and is carried entirely by the fallback).

// truncated/gzip-broken body (which `node-fetch@2` surfaces as
// `ERR_STREAM_PREMATURE_CLOSE`) or a 502, aborting the whole release.
// 6. Wraps the upstream delegation in a try/catch with a network-free
// fallback line, so a transient failure on one batch degrades a single

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.

Minor wording: "degrades a single entry" undersells it. When an oversized getReleaseLine batch fails, every .load() sharing that one fetch rejects together, so a whole batch (~UPSTREAM_CONCURRENCY, up to 25 entries) drops to the offline line at once — not one. The important half ("instead of failing the release") still holds; just the count is off.

options,
);
try {
return await limitUpstream(() =>

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.

The concurrency gate bounds the getReleaseLine batch nicely, but it does not bound this path's batch. apply-release-plan's getChangelogEntry calls getDependencyReleaseLine once per release with relevantChangesets (every changeset that bumped that package's deps), and upstream fans out to one getInfo() per changeset inside a single internal Promise.all — so all of those .load()s land in one DataLoader batch within this one limiter slot. A package whose deps were bumped by hundreds of changesets (e.g. the many packages depending on @workflow/core) therefore still produces one oversized query here, exactly the shape this PR is fixing.

In practice the release stays safe: that query throws → catchofflineDependencyReleaseLine, which is lossless for dependency lines (they only render commit links). So no functional gap. But point 5's rationale — "splits its lookups across several small GraphQL queries" — only really applies to getReleaseLine; this path is carried by the fallback, not by batch-splitting. Worth a word in the comments so the next reader doesn't assume both paths are bounded (or, if you want it truly bounded, gate the per-getInfo fan-out rather than the outer call).

// the changelog (the entry title would become literally `pr: #N`). The
// rewrite only touches `summary`; `changeset.commit` is unchanged, so the
// offline commit link is identical either way.
return offlineReleaseLine(changeset, options);

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.

Good fix on the backport-rewrite leak. One residual of the same class: if a changeset's summary manually contains a pr:/commit:/author: directive, maybeRewriteChangesetForBackport returns it unchanged (the guard at L309), upstream would strip it before rendering — but the offline path renders it verbatim, so the entry title becomes literally pr: #123 (confirmed locally). Much rarer than the backport case (needs a hand-written directive and an upstream failure), so not a blocker. If you want the offline path to fully match upstream, strip the leading directive lines here the same way upstream's .replace(/^\s*(?:pr|pull|...|author|user):/im, '') chain does.

: ` \`${short}\``;
}

function offlineReleaseLine(changeset, options) {

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.

Optional: these offline helpers + the limiter are pure and easy to unit-test, and the directive leak already regressed once (needed a follow-up commit), which is a good signal this file is edit-fragile. A tiny test — force upstream to throw and assert the offline line keeps the commit link + a clean title, plus assert 120 calls never exceed 25 concurrent — would be cheap regression insurance. No test runner is wired for .changeset/ today, so low priority, but worth considering.

@VaguelySerious
VaguelySerious merged commit 7f04d87 into mainJun 26, 2026
112 of 113 checks passed
@VaguelySerious
VaguelySerious deleted the peter/fix-release-changelog-premature-close branch June 26, 2026 21:42
github-actionsBot added a commit that referenced this pull request Jun 26, 2026
…h size (#2669)
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #2671. (backport job run)

VaguelySerious added a commit that referenced this pull request Jun 26, 2026
…h size (#2669) (#2671)
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
pranaygp added a commit that referenced this pull request Jun 26, 2026
…edrive
* origin/main: (90 commits)
[ai] Fix "missing text part" stream crash by repairing UI message framing (#2537)
[ci] Fix release "Premature close" by bounding changelog GraphQL batch size (#2669)
[ai] Drop orphan UI chunks after negative-index reconnect (#2082)
Fix trace detail panel Input/Output flicker (selection-driven state machine) (#2637)
[world-local] Scope untagged startup recovery to untagged runs (#2667)
fix(world-postgres): defer loopback worker startup (#2657)
docs: deprecate DurableAgent and WorkflowChatTransport, point to AI SDK WorkflowAgent (#2616)
[web] Fix HTTP/2 in bundled server build so observability reads work (#2632)
[core] Fix turbo-mode step-body writes racing run_started (#2629)
fix(world-postgres): rename setup command (#2644)
chore: ignore workflow swc caches (#2640)
fix(ai): ignore intentional stream aborts (#2635)
fix(nitro): reload steps during Vite HMR (#2572)
[world] Make @workflow/world own its Zod dependency (#2622)
feat(web-shared): extend cn with custom tailwind-merge class groups (#2619)
[world-vercel] Enable HTTP/2 for the events API and stream writes (#2573)
Remove shadow from off-screen marker indicator button (#2614)
Fix discovery of dotted JS workflow imports (#2594)
Version Packages (beta) (#2597)
Thread occurredAt into web-shared entities (#2608)
...
# Conflicts:
#	packages/core/src/runtime/step-executor.ts
#	packages/world-vercel/src/http-client.ts
Sign up for freeto 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.

2 participants

@VaguelySerious@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [ci] Fix release "Premature close" by bounding changelog GraphQL batch size by VaguelySerious · Pull Request #2669 · vercel/workflow · GitHub
Skip to content

[ci] Fix release "Premature close" by bounding changelog GraphQL batch size - #2669

Merged
VaguelySerious merged 2 commits into
mainfrom
peter/fix-release-changelog-premature-close
Jun 26, 2026
Merged

[ci] Fix release "Premature close" by bounding changelog GraphQL batch size#2669
VaguelySerious merged 2 commits into
mainfrom
peter/fix-release-changelog-premature-close

Conversation

@VaguelySerious

Copy link
Copy Markdown
Member

Problem

The Release workflow's pnpm ci:version (changeset version) step has failed on the last ~10 runs (e.g. run 28264162169) with:

🦋 error FetchError: Invalid response body while trying to fetch https://api.github.com/graphql: Premature close
at Gunzip.<anonymous> (.../node-fetch@2.7.0/.../lib/index.js:217:52)
code: 'ERR_STREAM_PREMATURE_CLOSE'

Root cause

@changesets/changelog-github resolves each changelog entry via @changesets/get-github-info, whose DataLoader is constructed with no maxBatchSize. @changesets/apply-release-plan calls getReleaseLine for every changeset inside one Promise.all, so all commits fold into a single GraphQL query.

We currently have ~300 changesets accumulated (beta pre mode, not yet flushed). That one query grows past what GitHub's GraphQL endpoint will reliably serve. Reproduced against the live API with the exact query shape on Node 24 + node-fetch@2.7.0:

commits in one queryresult
50–290200 OK (~120KB gzip)
300flaky: 200 or broken body / invalid json "<html>…"
350502 Bad Gateway

GitHub returns a truncated/gzip-broken body — which node-fetch@2 surfaces as ERR_STREAM_PREMATURE_CLOSE — or a 502, aborting the whole release. It's deterministic now because the backlog is reliably past the threshold, and it only grows.

The custom .changeset/changelog.mjs wrapper already intends to "never want a flaky network call to break pnpm changeset version", but only guarded its own backport lookups — not the upstream.getReleaseLine delegation that runs node-fetch.

Fix

  1. Bound the batch size via a concurrency gate (25). Limiting how many upstream calls are in flight bounds the number of get-github-info.load() calls per tick, which bounds the DataLoader batch — so the work splits into several small queries GitHub serves reliably, preserving full PR/author attribution.
  2. Wrap the upstream delegation in try/catch with a network-free fallback line, so a transient failure on a single batch degrades one entry instead of failing the release (finally extends the wrapper's stated promise to the delegation path).

Validation (empirical, against the live GitHub API)

  • Ungated → 1 request of 120; gated p-limit(25)5 requests, max batch 25.
  • Through the real modified wrapper over 120 commits: 5 requests, max batch 25, all 120 lines retain their [\sha`](…)commit link +Thanks @…` attribution.
  • Forcing node-fetch to throw ERR_STREAM_PREMATURE_CLOSE: no throw, well-formed offline line with commit link + multi-line summary.

🤖 Generated with Claude Code

…h size
The Release workflow's `changeset version` step has failed on the last ~10
runs with `FetchError: ... api.github.com/graphql: Premature close`.
`@changesets/changelog-github` resolves each entry via
`@changesets/get-github-info`, whose DataLoader is created with no
`maxBatchSize`. `apply-release-plan` calls `getReleaseLine` for every
changeset in one `Promise.all`, so all commits fold into a single GraphQL
query. With ~300 changesets accumulated in beta pre mode, that query grows
past what GitHub's GraphQL endpoint reliably serves — it returns a
truncated/gzip-broken body (which node-fetch@2 surfaces as
ERR_STREAM_PREMATURE_CLOSE) or a 502, aborting the whole release.
The custom `.changeset/changelog.mjs` wrapper already aimed to never let a
network call break the release, but only guarded its own backport lookups,
not the upstream delegation that runs node-fetch.
Fix:
- Cap concurrent upstream calls (25) so the DataLoader batches split across
several small queries GitHub can serve, preserving full PR/author
attribution. Verified against the live API: 120 commits -> 5 requests of
<=25 instead of one batch of 120.
- Wrap the upstream delegation in try/catch with a network-free fallback
line, so a transient failure on one batch degrades one entry instead of
failing the release.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code ownersJune 26, 2026 21:09
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fcd41ca

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

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

vercelBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production144202301672
✅ 💻 Local Development160502191824
✅ 📦 Local Production160502191824
✅ 🐘 Local Postgres159302311824
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total7282010788360

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-turbopack14903
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable15101
✅ nitro-stable126026
✅ nuxt-stable126026
✅ sveltekit-stable14507
✅ vite-stable126026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable127025
✅ e2e-local-dev-tanstack-start-127025
✅ e2e-local-postgres-nest-stable126026
✅ e2e-local-postgres-tanstack-start-126026
✅ e2e-local-prod-nest-stable127025
✅ e2e-local-prod-tanstack-start-127025
✅ e2e-vercel-prod-tanstack-start125027

📋 View full workflow run

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.048s (+3.7%)1.006s (~)0.958s101.00x
💻 LocalNitro0.048s (+8.5% 🔺)1.006s (~)0.958s101.00x
💻 LocalNext.js (Turbopack)0.049s (~)1.006s (~)0.956s101.03x
🐘 PostgresNext.js (Turbopack)0.055s (-9.1% 🟢)1.011s (~)0.957s101.14x
🐘 PostgresExpress0.065s (-13.6% 🟢)1.012s (~)0.947s101.35x
🐘 PostgresNitro0.072s (~)1.013s (~)0.941s101.49x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.204s (-27.8% 🟢)1.652s (-13.6% 🟢)1.448s101.00x
▲ VercelNext.js (Turbopack)0.548s (-10.5% 🟢)2.528s (~)1.980s102.69x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.082s (~)2.006s (~)0.924s101.00x
💻 LocalExpress1.083s (+0.7%)2.007s (~)0.924s101.00x
🐘 PostgresNext.js (Turbopack)1.085s (-0.9%)2.010s (~)0.926s101.00x
💻 LocalNext.js (Turbopack)1.085s (+0.5%)2.006s (~)0.921s101.00x
🐘 PostgresExpress1.095s (~)2.010s (~)0.915s101.01x
🐘 PostgresNitro1.102s (+0.5%)2.010s (~)0.908s101.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.393s (+2.1%)3.035s (+0.9%)1.641s101.00x
▲ VercelNext.js (Turbopack)2.053s (-12.7% 🟢)3.736s (-0.7%)1.682s101.47x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.446s (~)11.022s (~)0.576s31.00x
💻 LocalExpress10.459s (~)11.024s (~)0.565s31.00x
🐘 PostgresNext.js (Turbopack)10.465s (-0.6%)11.015s (~)0.549s31.00x
💻 LocalNext.js (Turbopack)10.469s (~)11.023s (~)0.554s31.00x
🐘 PostgresNitro10.492s (~)11.018s (~)0.526s31.00x
🐘 PostgresExpress10.502s (~)11.020s (~)0.518s31.01x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express12.061s (+2.7%)14.083s (+4.4%)2.022s31.00x
▲ VercelNext.js (Turbopack)13.273s (+4.5%)15.530s (+5.7% 🔺)2.258s21.10x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)13.578s (~)14.027s (~)0.449s51.00x
💻 LocalNitro13.613s (~)14.027s (~)0.414s51.00x
💻 LocalExpress13.631s (+0.5%)14.027s (~)0.397s51.00x
🐘 PostgresNitro13.683s (~)14.020s (~)0.337s51.01x
🐘 PostgresExpress13.766s (+0.6%)14.016s (~)0.250s51.01x
🐘 PostgresNext.js (Turbopack)13.783s (~)14.219s (+1.4%)0.436s51.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express18.000s (+9.3% 🔺)20.026s (+9.4% 🔺)2.026s41.00x
▲ VercelNext.js (Turbopack)18.839s (+6.9% 🔺)20.609s (+6.5% 🔺)1.770s31.05x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express12.073s (-3.0%)12.875s (-1.2%)0.802s71.00x
💻 LocalNitro12.126s (+0.9%)13.026s (+3.0%)0.901s71.00x
💻 LocalNext.js (Turbopack)12.134s (-3.1%)13.025s (-1.1%)0.892s71.01x
💻 LocalExpress12.203s (+1.6%)13.027s (+6.1% 🔺)0.824s71.01x
🐘 PostgresNext.js (Turbopack)12.229s (-2.2%)13.017s (~)0.788s71.01x
🐘 PostgresNitro12.302s (-0.9%)13.022s (~)0.720s71.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express19.662s (+7.8% 🔺)21.742s (+8.4% 🔺)2.080s51.00x
▲ VercelNext.js (Turbopack)21.512s (+1.3%)23.389s (+1.6%)1.877s41.09x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.166s (-0.8%)2.008s (~)0.842s151.00x
🐘 PostgresNitro1.182s (-0.6%)2.008s (~)0.825s151.01x
🐘 PostgresNext.js (Turbopack)1.191s (-1.6%)2.007s (-3.3%)0.816s151.02x
💻 LocalNitro1.400s (+1.8%)2.006s (~)0.606s151.20x
💻 LocalExpress1.421s (+1.9%)2.007s (~)0.586s151.22x
💻 LocalNext.js (Turbopack)1.472s (+4.7%)2.007s (~)0.535s151.26x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.561s (+18.3% 🔺)4.068s (+5.3% 🔺)1.508s81.00x
▲ VercelNext.js (Turbopack)4.041s (+25.4% 🔺)5.752s (+17.5% 🔺)1.711s61.58x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.300s (-2.4%)3.008s (~)1.708s101.00x
🐘 PostgresExpress1.331s (-1.3%)2.317s (-7.7% 🟢)0.985s131.02x
🐘 PostgresNitro1.354s (+2.3%)2.394s (-12.5% 🟢)1.040s131.04x
💻 LocalNitro2.447s (-0.7%)2.918s (-3.0%)0.470s111.88x
💻 LocalExpress2.781s (+12.0% 🔺)3.109s (+3.3%)0.328s102.14x
💻 LocalNext.js (Turbopack)2.807s (+16.9% 🔺)3.109s (+3.3%)0.302s102.16x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.787s (+10.9% 🔺)4.403s (+8.4% 🔺)1.616s71.00x
▲ VercelNext.js (Turbopack)3.872s (+8.7% 🔺)6.078s (+16.8% 🔺)2.207s51.39x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.612s (~)4.012s (-5.9% 🟢)2.399s81.00x
🐘 PostgresNitro1.670s (~)4.441s (-6.1% 🟢)2.770s71.04x
🐘 PostgresNext.js (Turbopack)2.288s (-29.4% 🟢)5.182s (-13.9% 🟢)2.894s61.42x
💻 LocalNitro6.144s (-1.0%)6.816s (-2.8%)0.672s53.81x
💻 LocalExpress6.790s (+13.7% 🔺)7.416s (+15.6% 🔺)0.626s54.21x
💻 LocalNext.js (Turbopack)6.878s (+15.4% 🔺)8.268s (+14.6% 🔺)1.391s44.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express4.610s (+67.5% 🔺)6.681s (+53.2% 🔺)2.070s51.00x
▲ VercelNext.js (Turbopack)5.407s (+22.7% 🔺)7.280s (+16.6% 🔺)1.873s51.17x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.177s (~)2.008s (~)0.831s151.00x
🐘 PostgresNitro1.189s (+0.6%)2.008s (~)0.819s151.01x
🐘 PostgresNext.js (Turbopack)1.195s (~)2.006s (~)0.812s151.02x
💻 LocalNitro1.415s (+1.2%)2.007s (~)0.592s151.20x
💻 LocalExpress1.416s (+1.4%)2.007s (~)0.590s151.20x
💻 LocalNext.js (Turbopack)1.443s (~)2.006s (~)0.563s151.23x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.246s (+8.3% 🔺)3.921s (+6.6% 🔺)1.674s81.00x
▲ VercelNext.js (Turbopack)3.820s (+19.2% 🔺)5.341s (+10.0% 🔺)1.521s61.70x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.291s (-3.8%)2.316s (+0.9%)1.025s131.00x
🐘 PostgresNext.js (Turbopack)1.294s (-1.6%)3.010s (~)1.716s101.00x
🐘 PostgresNitro1.347s (+4.2%)2.317s (~)0.970s131.04x
💻 LocalNitro2.539s (+5.3% 🔺)3.009s (~)0.470s101.97x
💻 LocalExpress2.694s (+11.9% 🔺)3.009s (~)0.315s102.09x
💻 LocalNext.js (Turbopack)2.710s (+2.7%)3.010s (-3.2%)0.300s102.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.881s (+16.6% 🔺)4.624s (+20.8% 🔺)1.743s71.00x
▲ VercelNext.js (Turbopack)4.760s (+39.8% 🔺)6.645s (+38.4% 🔺)1.886s51.65x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.637s (+0.5%)4.447s (+10.9% 🔺)2.811s71.00x
🐘 PostgresNitro1.879s (+14.0% 🔺)4.870s (+17.6% 🔺)2.991s71.15x
🐘 PostgresNext.js (Turbopack)3.326s (+4.1%)6.215s (~)2.889s52.03x
💻 LocalNext.js (Turbopack)6.042s (+7.0% 🔺)6.618s (+6.5% 🔺)0.575s53.69x
💻 LocalExpress6.361s (+14.7% 🔺)7.017s (+12.9% 🔺)0.655s53.89x
💻 LocalNitro7.257s (+22.0% 🔺)8.020s (+24.9% 🔺)0.763s44.43x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.768s (+38.5% 🔺)5.671s (+29.3% 🔺)1.903s61.00x
▲ VercelNext.js (Turbopack)4.935s (-8.6% 🟢)6.674s (-6.1% 🟢)1.738s51.31x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.511s (-17.2% 🟢)1.006s (-1.9%)0.495s601.00x
🐘 PostgresExpress0.551s (-5.4% 🟢)1.023s (-1.8%)0.472s591.08x
🐘 PostgresNitro0.584s (+1.0%)1.006s (-1.6%)0.422s601.14x
💻 LocalNitro0.585s (~)1.022s (~)0.437s591.15x
💻 LocalExpress0.590s (+5.8% 🔺)1.005s (~)0.416s601.15x
💻 LocalNext.js (Turbopack)0.611s (+2.0%)1.022s (+1.7%)0.411s591.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.182s (+29.4% 🔺)5.219s (+35.4% 🔺)2.037s121.00x
▲ VercelNext.js (Turbopack)4.964s (+32.2% 🔺)6.751s (+30.3% 🔺)1.786s91.56x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.229s (-11.6% 🟢)2.007s (~)0.778s451.00x
🐘 PostgresExpress1.345s (-3.1%)2.053s (~)0.708s441.09x
🐘 PostgresNitro1.412s (+7.7% 🔺)2.054s (+2.3%)0.642s441.15x
💻 LocalNitro1.478s (+3.6%)2.006s (~)0.528s451.20x
💻 LocalExpress1.492s (+3.0%)2.006s (-1.1%)0.514s451.21x
💻 LocalNext.js (Turbopack)1.498s (~)2.006s (~)0.508s451.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express8.097s (+38.5% 🔺)10.089s (+35.8% 🔺)1.992s91.00x
▲ VercelNext.js (Turbopack)9.498s (+12.5% 🔺)11.514s (+13.2% 🔺)2.017s81.17x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)2.413s (-13.6% 🟢)3.008s (-3.4%)0.594s401.00x
🐘 PostgresExpress2.517s (-7.9% 🟢)3.034s (-1.7%)0.517s401.04x
🐘 PostgresNitro2.726s (+0.7%)3.086s (-1.6%)0.360s391.13x
💻 LocalNitro3.138s (-2.1%)3.913s (+2.4%)0.775s311.30x
💻 LocalNext.js (Turbopack)3.225s (-1.7%)3.978s (-0.8%)0.753s311.34x
💻 LocalExpress3.300s (+4.4%)4.010s (+5.8% 🔺)0.710s301.37x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express18.146s (+52.1% 🔺)20.493s (+46.9% 🔺)2.347s71.00x
▲ VercelNext.js (Turbopack)22.977s (+21.7% 🔺)24.987s (+19.6% 🔺)2.011s51.27x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.191s (+1.6%)1.023s (+1.5%)0.832s591.00x
🐘 PostgresExpress0.201s (-9.8% 🟢)1.006s (~)0.805s601.05x
🐘 PostgresNitro0.214s (-1.8%)1.006s (~)0.792s601.12x
💻 LocalNitro0.462s (+7.7% 🔺)1.005s (~)0.543s602.41x
💻 LocalExpress0.483s (+13.1% 🔺)1.005s (~)0.522s602.52x
💻 LocalNext.js (Turbopack)0.628s (+6.0% 🔺)1.022s (+1.7%)0.394s593.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.370s (+19.6% 🔺)2.945s (+15.5% 🔺)1.575s211.00x
▲ VercelNext.js (Turbopack)2.815s (+36.1% 🔺)4.511s (+21.4% 🔺)1.696s142.05x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.270s (-9.4% 🟢)1.006s (-1.2%)0.736s901.00x
🐘 PostgresExpress0.318s (-5.9% 🟢)1.006s (-1.1%)0.688s901.18x
🐘 PostgresNitro0.345s (+1.2%)1.007s (~)0.662s901.28x
💻 LocalNitro2.079s (-4.1%)2.686s (-4.0%)0.607s347.71x
💻 LocalExpress2.164s (+2.0%)2.767s (+1.1%)0.603s338.02x
💻 LocalNext.js (Turbopack)2.792s (+7.0% 🔺)3.258s (+4.6%)0.466s2810.35x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.990s (+36.3% 🔺)3.997s (+30.3% 🔺)2.006s231.00x
▲ VercelNext.js (Turbopack)3.585s (+23.3% 🔺)5.408s (+12.6% 🔺)1.823s171.80x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.478s (-9.6% 🟢)3.010s (-1.7%)2.532s401.00x
🐘 PostgresExpress0.532s (-2.6%)1.149s (+3.7%)0.618s1051.11x
🐘 PostgresNitro0.545s (-3.0%)1.050s (-3.5%)0.505s1151.14x
💻 LocalNitro10.004s (+6.8% 🔺)11.031s (+5.6% 🔺)1.027s1120.93x
💻 LocalNext.js (Turbopack)10.057s (+6.1% 🔺)11.211s (+6.5% 🔺)1.155s1121.04x
💻 LocalExpress10.417s (+13.4% 🔺)11.305s (+9.1% 🔺)0.887s1121.79x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.916s (+52.4% 🔺)4.704s (+22.2% 🔺)1.788s261.00x
▲ VercelNext.js (Turbopack)5.493s (+46.5% 🔺)7.471s (+35.5% 🔺)1.979s171.88x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.137s (-1.3%)1.969s (~)0.012s (+17.0% 🔺)2.020s (~)0.883s101.00x
🐘 PostgresExpress1.145s (-1.1%)2.000s (~)0.001s (-21.4% 🟢)2.010s (~)0.865s101.01x
🐘 PostgresNext.js (Turbopack)1.153s (-1.0%)2.000s (~)0.001s (+30.0% 🔺)2.010s (~)0.857s101.01x
💻 LocalNitro1.160s (+0.8%)2.004s (~)0.012s (+9.7% 🔺)2.020s (~)0.859s101.02x
💻 LocalExpress1.164s (+1.1%)2.005s (~)0.012s (+22.8% 🔺)2.021s (~)0.856s101.02x
🐘 PostgresNitro1.166s (-2.1%)1.999s (~)0.001s (-26.7% 🟢)2.011s (~)0.845s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.974s (-7.9% 🟢)3.569s (+9.0% 🔺)2.038s (+21.1% 🔺)6.129s (+12.0% 🔺)4.155s101.00x
▲ VercelNext.js (Turbopack)3.986s (+28.8% 🔺)4.016s (+6.1% 🔺)1.953s (+42.3% 🔺)7.748s (+23.1% 🔺)3.761s102.02x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.540s (-0.8%)2.003s (~)0.005s (+4.7%)2.026s (~)0.486s301.00x
💻 LocalNitro1.547s (+0.6%)2.009s (~)0.013s (+3.7%)2.025s (~)0.479s301.00x
🐘 PostgresNext.js (Turbopack)1.558s (-3.7%)2.009s (~)0.005s (+2.6%)2.026s (~)0.467s301.01x
💻 LocalExpress1.574s (+2.7%)2.011s (~)0.013s (+0.8%)2.026s (~)0.453s301.02x
💻 LocalNext.js (Turbopack)1.584s (~)1.971s (~)0.011s (-23.6% 🟢)2.024s (~)0.441s301.03x
🐘 PostgresNitro1.597s (+0.7%)2.002s (~)0.005s (+3.4%)2.026s (~)0.428s301.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express6.367s (+16.0% 🔺)7.885s (+13.5% 🔺)0.250s (+8.7% 🔺)8.629s (+11.5% 🔺)2.261s71.00x
▲ VercelNext.js (Turbopack)10.932s (+18.7% 🔺)11.820s (+8.5% 🔺)0.243s (-3.3%)13.252s (+14.1% 🔺)2.320s51.72x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.769s (-6.4% 🟢)1.065s (~)0.000s (-32.1% 🟢)1.102s (+2.2%)0.333s551.00x
🐘 PostgresNitro0.806s (-1.8%)1.045s (-5.2% 🟢)0.000s (-36.8% 🟢)1.060s (-5.4% 🟢)0.253s571.05x
🐘 PostgresNext.js (Turbopack)1.006s (+0.8%)1.418s (-2.9%)0.000s (-100.0% 🟢)1.429s (-3.7%)0.422s431.31x
💻 LocalExpress1.240s (+0.6%)2.013s (~)0.000s (-60.0% 🟢)2.015s (~)0.776s301.61x
💻 LocalNitro1.268s (~)2.013s (~)0.000s (+14.3% 🔺)2.016s (~)0.748s301.65x
💻 LocalNext.js (Turbopack)1.354s (-2.9%)1.979s (~)0.001s (+77.8% 🔺)2.016s (~)0.662s301.76x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.701s (+27.1% 🔺)5.023s (+15.1% 🔺)0.002s (+Infinity% 🔺)5.526s (+14.0% 🔺)1.825s111.00x
▲ VercelNext.js (Turbopack)5.168s (+6.3% 🔺)6.181s (+10.6% 🔺)0.000s (NaN%)6.952s (+1.2%)1.784s91.40x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.703s (+0.7%)2.301s (~)0.000s (+Infinity% 🔺)2.316s (~)0.613s261.00x
🐘 PostgresNitro1.770s (-3.3%)2.301s (-3.2%)0.000s (-100.0% 🟢)2.332s (-3.0%)0.562s261.04x
🐘 PostgresNext.js (Turbopack)2.551s (-17.5% 🟢)3.051s (-13.6% 🟢)0.000s (-71.7% 🟢)3.059s (-13.6% 🟢)0.508s201.50x
💻 LocalNitro3.469s (+5.1% 🔺)4.095s (+1.7%)0.000s (-30.0% 🟢)4.099s (+1.7%)0.630s152.04x
💻 LocalExpress3.538s (+7.8% 🔺)4.027s (+4.9%)0.001s (+113.3% 🔺)4.030s (+4.9%)0.492s152.08x
💻 LocalNext.js (Turbopack)3.541s (~)4.058s (+1.7%)0.001s (+113.3% 🔺)4.098s (+1.4%)0.557s152.08x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.371s (+2.8%)6.997s (+7.5% 🔺)0.000s (-100.0% 🟢)7.482s (+7.0% 🔺)2.111s91.00x
▲ VercelNext.js (Turbopack)8.546s (+24.9% 🔺)8.850s (+15.0% 🔺)0.000s (-100.0% 🟢)10.343s (+19.1% 🔺)1.797s61.59x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro16/21
🐘 PostgresNext.js (Turbopack)10/21
▲ VercelExpress21/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres17/21
Next.js (Turbopack)🐘 Postgres17/21
Nitro🐘 Postgres14/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)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

Comment thread.changeset/changelog.mjs Outdated
…tten summary verbatim, leaking injected `pr:`/`commit:` directive lines into the changelog so the entry title becomes literally `pr: #N`.
This commit fixes the issue reported at .changeset/changelog.mjs:368
## Bug
`maybeRewriteChangesetForBackport` prepends directive lines to a backport changeset's summary:
```js
const rewrittenSummary =
`pr: #${originalPRNumber}\n` +
`commit: ${changeset.commit}\n` +
summary;
return { ...changeset, summary: rewrittenSummary };
```
The upstream `@changesets/changelog-github` `getReleaseLine` consumes those directives and **strips them** from the summary (via `.replace(/^\s*(?:pr|pull|pull\s+request):.../im, ...)`, `commit:`, `author:` etc.) before splitting into `firstLine`/`futureLines` and rendering. So in the normal path the directives never appear in the output — that's the whole point of injecting them.
The offline fallback, however, does **not** strip them:
```js
function offlineReleaseLine(changeset, options) {
const [firstLine, ...futureLines] = (changeset.summary ?? '')
.split('\n')
.map((l) => l.trimEnd());
...
}
```
It just splits on `\n` and renders verbatim.
### Concrete trigger
A backport changeset where:
1. `maybeRewriteChangesetForBackport` **succeeds** (network up, PR resolved, directives injected → `rewritten.summary` starts with `pr: #N\ncommit: <sha>\n`), and
2. the subsequent `limitUpstream(() => upstream.getReleaseLine(rewritten, ...))` **throws** — exactly the flaky/oversized GraphQL batch scenario this fallback was added for.
The catch block then ran `offlineReleaseLine(rewritten, options)`, producing:
```
- [`abc1234`](...) - pr: #123
commit: abc1234...
<real first line>
```
The entry title becomes literally `pr: #123` and `commit: …` leaks as body text.
## Fix
Pass the **original** `changeset` to `offlineReleaseLine` instead of `rewritten`. The backport rewrite only mutates `summary`; `changeset.commit` is untouched, so the offline commit link is identical, while the original summary has no injected directives. The offline path already drops PR/author attribution it can't resolve, so using the un-rewritten summary is exactly correct.
The dependency-line path (`offlineDependencyReleaseLine`) is unaffected since no directive injection happens there.
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: VaguelySerious <mittgfu@gmail.com>

@pranaygppranaygp 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.

Reviewed the diff and verified the core mechanism end to end against the real file (stubbed upstream, Node 22.18, require()-able ESM confirmed):

  • Limiter works as claimed: 120 getReleaseLine calls → max 25 concurrent → ~5 DataLoader batches of ≤25, order preserved. Since get-github-info's DataLoader is a module-level singleton built with no maxBatchSize (confirmed in get-github-info@0.7.0 src) and each getReleaseLine fires exactly one .load(), gating concurrency at 25 genuinely bounds the per-batch query size while preserving full PR/author attribution. Good, well-reasoned fix.
  • Offline getReleaseLine mirrors the upstream template exactly (changelog-github@0.5.2): `\n\n-${prefix ? ` ${prefix} -` : ''} ${firstLine}...`, dropping only the pull/Thanks attribution. The 2nd-commit fix (feed the original changeset, not rewritten) is correct and I confirmed it no longer leaks pr:/commit: directives.
  • Offline dependency line is lossless — dep lines only ever render commit links, which you reconstruct from the SHA, so that path loses nothing even when it falls back.

LGTM to ship — it unblocks the release. A few non-blocking notes inline (one substantive: the dependency-rollup path isn't actually batch-bounded by the limiter and is carried entirely by the fallback).

// truncated/gzip-broken body (which `node-fetch@2` surfaces as
// `ERR_STREAM_PREMATURE_CLOSE`) or a 502, aborting the whole release.
// 6. Wraps the upstream delegation in a try/catch with a network-free
// fallback line, so a transient failure on one batch degrades a single

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.

Minor wording: "degrades a single entry" undersells it. When an oversized getReleaseLine batch fails, every .load() sharing that one fetch rejects together, so a whole batch (~UPSTREAM_CONCURRENCY, up to 25 entries) drops to the offline line at once — not one. The important half ("instead of failing the release") still holds; just the count is off.

options,
);
try {
return await limitUpstream(() =>

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.

The concurrency gate bounds the getReleaseLine batch nicely, but it does not bound this path's batch. apply-release-plan's getChangelogEntry calls getDependencyReleaseLine once per release with relevantChangesets (every changeset that bumped that package's deps), and upstream fans out to one getInfo() per changeset inside a single internal Promise.all — so all of those .load()s land in one DataLoader batch within this one limiter slot. A package whose deps were bumped by hundreds of changesets (e.g. the many packages depending on @workflow/core) therefore still produces one oversized query here, exactly the shape this PR is fixing.

In practice the release stays safe: that query throws → catchofflineDependencyReleaseLine, which is lossless for dependency lines (they only render commit links). So no functional gap. But point 5's rationale — "splits its lookups across several small GraphQL queries" — only really applies to getReleaseLine; this path is carried by the fallback, not by batch-splitting. Worth a word in the comments so the next reader doesn't assume both paths are bounded (or, if you want it truly bounded, gate the per-getInfo fan-out rather than the outer call).

// the changelog (the entry title would become literally `pr: #N`). The
// rewrite only touches `summary`; `changeset.commit` is unchanged, so the
// offline commit link is identical either way.
return offlineReleaseLine(changeset, options);

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.

Good fix on the backport-rewrite leak. One residual of the same class: if a changeset's summary manually contains a pr:/commit:/author: directive, maybeRewriteChangesetForBackport returns it unchanged (the guard at L309), upstream would strip it before rendering — but the offline path renders it verbatim, so the entry title becomes literally pr: #123 (confirmed locally). Much rarer than the backport case (needs a hand-written directive and an upstream failure), so not a blocker. If you want the offline path to fully match upstream, strip the leading directive lines here the same way upstream's .replace(/^\s*(?:pr|pull|...|author|user):/im, '') chain does.

: ` \`${short}\``;
}

function offlineReleaseLine(changeset, options) {

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.

Optional: these offline helpers + the limiter are pure and easy to unit-test, and the directive leak already regressed once (needed a follow-up commit), which is a good signal this file is edit-fragile. A tiny test — force upstream to throw and assert the offline line keeps the commit link + a clean title, plus assert 120 calls never exceed 25 concurrent — would be cheap regression insurance. No test runner is wired for .changeset/ today, so low priority, but worth considering.

@VaguelySerious
VaguelySerious merged commit 7f04d87 into mainJun 26, 2026
112 of 113 checks passed
@VaguelySerious
VaguelySerious deleted the peter/fix-release-changelog-premature-close branch June 26, 2026 21:42
github-actionsBot added a commit that referenced this pull request Jun 26, 2026
…h size (#2669)
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #2671. (backport job run)

VaguelySerious added a commit that referenced this pull request Jun 26, 2026
…h size (#2669) (#2671)
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
pranaygp added a commit that referenced this pull request Jun 26, 2026
…edrive
* origin/main: (90 commits)
[ai] Fix "missing text part" stream crash by repairing UI message framing (#2537)
[ci] Fix release "Premature close" by bounding changelog GraphQL batch size (#2669)
[ai] Drop orphan UI chunks after negative-index reconnect (#2082)
Fix trace detail panel Input/Output flicker (selection-driven state machine) (#2637)
[world-local] Scope untagged startup recovery to untagged runs (#2667)
fix(world-postgres): defer loopback worker startup (#2657)
docs: deprecate DurableAgent and WorkflowChatTransport, point to AI SDK WorkflowAgent (#2616)
[web] Fix HTTP/2 in bundled server build so observability reads work (#2632)
[core] Fix turbo-mode step-body writes racing run_started (#2629)
fix(world-postgres): rename setup command (#2644)
chore: ignore workflow swc caches (#2640)
fix(ai): ignore intentional stream aborts (#2635)
fix(nitro): reload steps during Vite HMR (#2572)
[world] Make @workflow/world own its Zod dependency (#2622)
feat(web-shared): extend cn with custom tailwind-merge class groups (#2619)
[world-vercel] Enable HTTP/2 for the events API and stream writes (#2573)
Remove shadow from off-screen marker indicator button (#2614)
Fix discovery of dotted JS workflow imports (#2594)
Version Packages (beta) (#2597)
Thread occurredAt into web-shared entities (#2608)
...
# Conflicts:
#	packages/core/src/runtime/step-executor.ts
#	packages/world-vercel/src/http-client.ts
Sign up for freeto 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.

2 participants

@VaguelySerious@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' [ci] Fix release "Premature close" by bounding changelog GraphQL batch size by VaguelySerious · Pull Request #2669 · vercel/workflow · GitHub
Skip to content

[ci] Fix release "Premature close" by bounding changelog GraphQL batch size - #2669

Merged
VaguelySerious merged 2 commits into
mainfrom
peter/fix-release-changelog-premature-close
Jun 26, 2026
Merged

[ci] Fix release "Premature close" by bounding changelog GraphQL batch size#2669
VaguelySerious merged 2 commits into
mainfrom
peter/fix-release-changelog-premature-close

Conversation

@VaguelySerious

Copy link
Copy Markdown
Member

Problem

The Release workflow's pnpm ci:version (changeset version) step has failed on the last ~10 runs (e.g. run 28264162169) with:

🦋 error FetchError: Invalid response body while trying to fetch https://api.github.com/graphql: Premature close
at Gunzip.<anonymous> (.../node-fetch@2.7.0/.../lib/index.js:217:52)
code: 'ERR_STREAM_PREMATURE_CLOSE'

Root cause

@changesets/changelog-github resolves each changelog entry via @changesets/get-github-info, whose DataLoader is constructed with no maxBatchSize. @changesets/apply-release-plan calls getReleaseLine for every changeset inside one Promise.all, so all commits fold into a single GraphQL query.

We currently have ~300 changesets accumulated (beta pre mode, not yet flushed). That one query grows past what GitHub's GraphQL endpoint will reliably serve. Reproduced against the live API with the exact query shape on Node 24 + node-fetch@2.7.0:

commits in one queryresult
50–290200 OK (~120KB gzip)
300flaky: 200 or broken body / invalid json "<html>…"
350502 Bad Gateway

GitHub returns a truncated/gzip-broken body — which node-fetch@2 surfaces as ERR_STREAM_PREMATURE_CLOSE — or a 502, aborting the whole release. It's deterministic now because the backlog is reliably past the threshold, and it only grows.

The custom .changeset/changelog.mjs wrapper already intends to "never want a flaky network call to break pnpm changeset version", but only guarded its own backport lookups — not the upstream.getReleaseLine delegation that runs node-fetch.

Fix

  1. Bound the batch size via a concurrency gate (25). Limiting how many upstream calls are in flight bounds the number of get-github-info.load() calls per tick, which bounds the DataLoader batch — so the work splits into several small queries GitHub serves reliably, preserving full PR/author attribution.
  2. Wrap the upstream delegation in try/catch with a network-free fallback line, so a transient failure on a single batch degrades one entry instead of failing the release (finally extends the wrapper's stated promise to the delegation path).

Validation (empirical, against the live GitHub API)

  • Ungated → 1 request of 120; gated p-limit(25)5 requests, max batch 25.
  • Through the real modified wrapper over 120 commits: 5 requests, max batch 25, all 120 lines retain their [\sha`](…)commit link +Thanks @…` attribution.
  • Forcing node-fetch to throw ERR_STREAM_PREMATURE_CLOSE: no throw, well-formed offline line with commit link + multi-line summary.

🤖 Generated with Claude Code

…h size
The Release workflow's `changeset version` step has failed on the last ~10
runs with `FetchError: ... api.github.com/graphql: Premature close`.
`@changesets/changelog-github` resolves each entry via
`@changesets/get-github-info`, whose DataLoader is created with no
`maxBatchSize`. `apply-release-plan` calls `getReleaseLine` for every
changeset in one `Promise.all`, so all commits fold into a single GraphQL
query. With ~300 changesets accumulated in beta pre mode, that query grows
past what GitHub's GraphQL endpoint reliably serves — it returns a
truncated/gzip-broken body (which node-fetch@2 surfaces as
ERR_STREAM_PREMATURE_CLOSE) or a 502, aborting the whole release.
The custom `.changeset/changelog.mjs` wrapper already aimed to never let a
network call break the release, but only guarded its own backport lookups,
not the upstream delegation that runs node-fetch.
Fix:
- Cap concurrent upstream calls (25) so the DataLoader batches split across
several small queries GitHub can serve, preserving full PR/author
attribution. Verified against the live API: 120 commits -> 5 requests of
<=25 instead of one batch of 120.
- Wrap the upstream delegation in try/catch with a network-free fallback
line, so a transient failure on one batch degrades one entry instead of
failing the release.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code ownersJune 26, 2026 21:09
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fcd41ca

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

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

vercelBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production144202301672
✅ 💻 Local Development160502191824
✅ 📦 Local Production160502191824
✅ 🐘 Local Postgres159302311824
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total7282010788360

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-turbopack14903
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable15101
✅ nitro-stable126026
✅ nuxt-stable126026
✅ sveltekit-stable14507
✅ vite-stable126026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable127025
✅ e2e-local-dev-tanstack-start-127025
✅ e2e-local-postgres-nest-stable126026
✅ e2e-local-postgres-tanstack-start-126026
✅ e2e-local-prod-nest-stable127025
✅ e2e-local-prod-tanstack-start-127025
✅ e2e-vercel-prod-tanstack-start125027

📋 View full workflow run

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.048s (+3.7%)1.006s (~)0.958s101.00x
💻 LocalNitro0.048s (+8.5% 🔺)1.006s (~)0.958s101.00x
💻 LocalNext.js (Turbopack)0.049s (~)1.006s (~)0.956s101.03x
🐘 PostgresNext.js (Turbopack)0.055s (-9.1% 🟢)1.011s (~)0.957s101.14x
🐘 PostgresExpress0.065s (-13.6% 🟢)1.012s (~)0.947s101.35x
🐘 PostgresNitro0.072s (~)1.013s (~)0.941s101.49x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.204s (-27.8% 🟢)1.652s (-13.6% 🟢)1.448s101.00x
▲ VercelNext.js (Turbopack)0.548s (-10.5% 🟢)2.528s (~)1.980s102.69x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.082s (~)2.006s (~)0.924s101.00x
💻 LocalExpress1.083s (+0.7%)2.007s (~)0.924s101.00x
🐘 PostgresNext.js (Turbopack)1.085s (-0.9%)2.010s (~)0.926s101.00x
💻 LocalNext.js (Turbopack)1.085s (+0.5%)2.006s (~)0.921s101.00x
🐘 PostgresExpress1.095s (~)2.010s (~)0.915s101.01x
🐘 PostgresNitro1.102s (+0.5%)2.010s (~)0.908s101.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.393s (+2.1%)3.035s (+0.9%)1.641s101.00x
▲ VercelNext.js (Turbopack)2.053s (-12.7% 🟢)3.736s (-0.7%)1.682s101.47x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.446s (~)11.022s (~)0.576s31.00x
💻 LocalExpress10.459s (~)11.024s (~)0.565s31.00x
🐘 PostgresNext.js (Turbopack)10.465s (-0.6%)11.015s (~)0.549s31.00x
💻 LocalNext.js (Turbopack)10.469s (~)11.023s (~)0.554s31.00x
🐘 PostgresNitro10.492s (~)11.018s (~)0.526s31.00x
🐘 PostgresExpress10.502s (~)11.020s (~)0.518s31.01x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express12.061s (+2.7%)14.083s (+4.4%)2.022s31.00x
▲ VercelNext.js (Turbopack)13.273s (+4.5%)15.530s (+5.7% 🔺)2.258s21.10x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)13.578s (~)14.027s (~)0.449s51.00x
💻 LocalNitro13.613s (~)14.027s (~)0.414s51.00x
💻 LocalExpress13.631s (+0.5%)14.027s (~)0.397s51.00x
🐘 PostgresNitro13.683s (~)14.020s (~)0.337s51.01x
🐘 PostgresExpress13.766s (+0.6%)14.016s (~)0.250s51.01x
🐘 PostgresNext.js (Turbopack)13.783s (~)14.219s (+1.4%)0.436s51.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express18.000s (+9.3% 🔺)20.026s (+9.4% 🔺)2.026s41.00x
▲ VercelNext.js (Turbopack)18.839s (+6.9% 🔺)20.609s (+6.5% 🔺)1.770s31.05x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express12.073s (-3.0%)12.875s (-1.2%)0.802s71.00x
💻 LocalNitro12.126s (+0.9%)13.026s (+3.0%)0.901s71.00x
💻 LocalNext.js (Turbopack)12.134s (-3.1%)13.025s (-1.1%)0.892s71.01x
💻 LocalExpress12.203s (+1.6%)13.027s (+6.1% 🔺)0.824s71.01x
🐘 PostgresNext.js (Turbopack)12.229s (-2.2%)13.017s (~)0.788s71.01x
🐘 PostgresNitro12.302s (-0.9%)13.022s (~)0.720s71.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express19.662s (+7.8% 🔺)21.742s (+8.4% 🔺)2.080s51.00x
▲ VercelNext.js (Turbopack)21.512s (+1.3%)23.389s (+1.6%)1.877s41.09x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.166s (-0.8%)2.008s (~)0.842s151.00x
🐘 PostgresNitro1.182s (-0.6%)2.008s (~)0.825s151.01x
🐘 PostgresNext.js (Turbopack)1.191s (-1.6%)2.007s (-3.3%)0.816s151.02x
💻 LocalNitro1.400s (+1.8%)2.006s (~)0.606s151.20x
💻 LocalExpress1.421s (+1.9%)2.007s (~)0.586s151.22x
💻 LocalNext.js (Turbopack)1.472s (+4.7%)2.007s (~)0.535s151.26x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.561s (+18.3% 🔺)4.068s (+5.3% 🔺)1.508s81.00x
▲ VercelNext.js (Turbopack)4.041s (+25.4% 🔺)5.752s (+17.5% 🔺)1.711s61.58x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.300s (-2.4%)3.008s (~)1.708s101.00x
🐘 PostgresExpress1.331s (-1.3%)2.317s (-7.7% 🟢)0.985s131.02x
🐘 PostgresNitro1.354s (+2.3%)2.394s (-12.5% 🟢)1.040s131.04x
💻 LocalNitro2.447s (-0.7%)2.918s (-3.0%)0.470s111.88x
💻 LocalExpress2.781s (+12.0% 🔺)3.109s (+3.3%)0.328s102.14x
💻 LocalNext.js (Turbopack)2.807s (+16.9% 🔺)3.109s (+3.3%)0.302s102.16x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.787s (+10.9% 🔺)4.403s (+8.4% 🔺)1.616s71.00x
▲ VercelNext.js (Turbopack)3.872s (+8.7% 🔺)6.078s (+16.8% 🔺)2.207s51.39x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.612s (~)4.012s (-5.9% 🟢)2.399s81.00x
🐘 PostgresNitro1.670s (~)4.441s (-6.1% 🟢)2.770s71.04x
🐘 PostgresNext.js (Turbopack)2.288s (-29.4% 🟢)5.182s (-13.9% 🟢)2.894s61.42x
💻 LocalNitro6.144s (-1.0%)6.816s (-2.8%)0.672s53.81x
💻 LocalExpress6.790s (+13.7% 🔺)7.416s (+15.6% 🔺)0.626s54.21x
💻 LocalNext.js (Turbopack)6.878s (+15.4% 🔺)8.268s (+14.6% 🔺)1.391s44.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express4.610s (+67.5% 🔺)6.681s (+53.2% 🔺)2.070s51.00x
▲ VercelNext.js (Turbopack)5.407s (+22.7% 🔺)7.280s (+16.6% 🔺)1.873s51.17x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.177s (~)2.008s (~)0.831s151.00x
🐘 PostgresNitro1.189s (+0.6%)2.008s (~)0.819s151.01x
🐘 PostgresNext.js (Turbopack)1.195s (~)2.006s (~)0.812s151.02x
💻 LocalNitro1.415s (+1.2%)2.007s (~)0.592s151.20x
💻 LocalExpress1.416s (+1.4%)2.007s (~)0.590s151.20x
💻 LocalNext.js (Turbopack)1.443s (~)2.006s (~)0.563s151.23x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.246s (+8.3% 🔺)3.921s (+6.6% 🔺)1.674s81.00x
▲ VercelNext.js (Turbopack)3.820s (+19.2% 🔺)5.341s (+10.0% 🔺)1.521s61.70x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.291s (-3.8%)2.316s (+0.9%)1.025s131.00x
🐘 PostgresNext.js (Turbopack)1.294s (-1.6%)3.010s (~)1.716s101.00x
🐘 PostgresNitro1.347s (+4.2%)2.317s (~)0.970s131.04x
💻 LocalNitro2.539s (+5.3% 🔺)3.009s (~)0.470s101.97x
💻 LocalExpress2.694s (+11.9% 🔺)3.009s (~)0.315s102.09x
💻 LocalNext.js (Turbopack)2.710s (+2.7%)3.010s (-3.2%)0.300s102.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.881s (+16.6% 🔺)4.624s (+20.8% 🔺)1.743s71.00x
▲ VercelNext.js (Turbopack)4.760s (+39.8% 🔺)6.645s (+38.4% 🔺)1.886s51.65x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.637s (+0.5%)4.447s (+10.9% 🔺)2.811s71.00x
🐘 PostgresNitro1.879s (+14.0% 🔺)4.870s (+17.6% 🔺)2.991s71.15x
🐘 PostgresNext.js (Turbopack)3.326s (+4.1%)6.215s (~)2.889s52.03x
💻 LocalNext.js (Turbopack)6.042s (+7.0% 🔺)6.618s (+6.5% 🔺)0.575s53.69x
💻 LocalExpress6.361s (+14.7% 🔺)7.017s (+12.9% 🔺)0.655s53.89x
💻 LocalNitro7.257s (+22.0% 🔺)8.020s (+24.9% 🔺)0.763s44.43x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.768s (+38.5% 🔺)5.671s (+29.3% 🔺)1.903s61.00x
▲ VercelNext.js (Turbopack)4.935s (-8.6% 🟢)6.674s (-6.1% 🟢)1.738s51.31x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.511s (-17.2% 🟢)1.006s (-1.9%)0.495s601.00x
🐘 PostgresExpress0.551s (-5.4% 🟢)1.023s (-1.8%)0.472s591.08x
🐘 PostgresNitro0.584s (+1.0%)1.006s (-1.6%)0.422s601.14x
💻 LocalNitro0.585s (~)1.022s (~)0.437s591.15x
💻 LocalExpress0.590s (+5.8% 🔺)1.005s (~)0.416s601.15x
💻 LocalNext.js (Turbopack)0.611s (+2.0%)1.022s (+1.7%)0.411s591.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.182s (+29.4% 🔺)5.219s (+35.4% 🔺)2.037s121.00x
▲ VercelNext.js (Turbopack)4.964s (+32.2% 🔺)6.751s (+30.3% 🔺)1.786s91.56x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.229s (-11.6% 🟢)2.007s (~)0.778s451.00x
🐘 PostgresExpress1.345s (-3.1%)2.053s (~)0.708s441.09x
🐘 PostgresNitro1.412s (+7.7% 🔺)2.054s (+2.3%)0.642s441.15x
💻 LocalNitro1.478s (+3.6%)2.006s (~)0.528s451.20x
💻 LocalExpress1.492s (+3.0%)2.006s (-1.1%)0.514s451.21x
💻 LocalNext.js (Turbopack)1.498s (~)2.006s (~)0.508s451.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express8.097s (+38.5% 🔺)10.089s (+35.8% 🔺)1.992s91.00x
▲ VercelNext.js (Turbopack)9.498s (+12.5% 🔺)11.514s (+13.2% 🔺)2.017s81.17x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)2.413s (-13.6% 🟢)3.008s (-3.4%)0.594s401.00x
🐘 PostgresExpress2.517s (-7.9% 🟢)3.034s (-1.7%)0.517s401.04x
🐘 PostgresNitro2.726s (+0.7%)3.086s (-1.6%)0.360s391.13x
💻 LocalNitro3.138s (-2.1%)3.913s (+2.4%)0.775s311.30x
💻 LocalNext.js (Turbopack)3.225s (-1.7%)3.978s (-0.8%)0.753s311.34x
💻 LocalExpress3.300s (+4.4%)4.010s (+5.8% 🔺)0.710s301.37x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express18.146s (+52.1% 🔺)20.493s (+46.9% 🔺)2.347s71.00x
▲ VercelNext.js (Turbopack)22.977s (+21.7% 🔺)24.987s (+19.6% 🔺)2.011s51.27x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.191s (+1.6%)1.023s (+1.5%)0.832s591.00x
🐘 PostgresExpress0.201s (-9.8% 🟢)1.006s (~)0.805s601.05x
🐘 PostgresNitro0.214s (-1.8%)1.006s (~)0.792s601.12x
💻 LocalNitro0.462s (+7.7% 🔺)1.005s (~)0.543s602.41x
💻 LocalExpress0.483s (+13.1% 🔺)1.005s (~)0.522s602.52x
💻 LocalNext.js (Turbopack)0.628s (+6.0% 🔺)1.022s (+1.7%)0.394s593.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.370s (+19.6% 🔺)2.945s (+15.5% 🔺)1.575s211.00x
▲ VercelNext.js (Turbopack)2.815s (+36.1% 🔺)4.511s (+21.4% 🔺)1.696s142.05x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.270s (-9.4% 🟢)1.006s (-1.2%)0.736s901.00x
🐘 PostgresExpress0.318s (-5.9% 🟢)1.006s (-1.1%)0.688s901.18x
🐘 PostgresNitro0.345s (+1.2%)1.007s (~)0.662s901.28x
💻 LocalNitro2.079s (-4.1%)2.686s (-4.0%)0.607s347.71x
💻 LocalExpress2.164s (+2.0%)2.767s (+1.1%)0.603s338.02x
💻 LocalNext.js (Turbopack)2.792s (+7.0% 🔺)3.258s (+4.6%)0.466s2810.35x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.990s (+36.3% 🔺)3.997s (+30.3% 🔺)2.006s231.00x
▲ VercelNext.js (Turbopack)3.585s (+23.3% 🔺)5.408s (+12.6% 🔺)1.823s171.80x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.478s (-9.6% 🟢)3.010s (-1.7%)2.532s401.00x
🐘 PostgresExpress0.532s (-2.6%)1.149s (+3.7%)0.618s1051.11x
🐘 PostgresNitro0.545s (-3.0%)1.050s (-3.5%)0.505s1151.14x
💻 LocalNitro10.004s (+6.8% 🔺)11.031s (+5.6% 🔺)1.027s1120.93x
💻 LocalNext.js (Turbopack)10.057s (+6.1% 🔺)11.211s (+6.5% 🔺)1.155s1121.04x
💻 LocalExpress10.417s (+13.4% 🔺)11.305s (+9.1% 🔺)0.887s1121.79x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.916s (+52.4% 🔺)4.704s (+22.2% 🔺)1.788s261.00x
▲ VercelNext.js (Turbopack)5.493s (+46.5% 🔺)7.471s (+35.5% 🔺)1.979s171.88x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.137s (-1.3%)1.969s (~)0.012s (+17.0% 🔺)2.020s (~)0.883s101.00x
🐘 PostgresExpress1.145s (-1.1%)2.000s (~)0.001s (-21.4% 🟢)2.010s (~)0.865s101.01x
🐘 PostgresNext.js (Turbopack)1.153s (-1.0%)2.000s (~)0.001s (+30.0% 🔺)2.010s (~)0.857s101.01x
💻 LocalNitro1.160s (+0.8%)2.004s (~)0.012s (+9.7% 🔺)2.020s (~)0.859s101.02x
💻 LocalExpress1.164s (+1.1%)2.005s (~)0.012s (+22.8% 🔺)2.021s (~)0.856s101.02x
🐘 PostgresNitro1.166s (-2.1%)1.999s (~)0.001s (-26.7% 🟢)2.011s (~)0.845s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.974s (-7.9% 🟢)3.569s (+9.0% 🔺)2.038s (+21.1% 🔺)6.129s (+12.0% 🔺)4.155s101.00x
▲ VercelNext.js (Turbopack)3.986s (+28.8% 🔺)4.016s (+6.1% 🔺)1.953s (+42.3% 🔺)7.748s (+23.1% 🔺)3.761s102.02x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.540s (-0.8%)2.003s (~)0.005s (+4.7%)2.026s (~)0.486s301.00x
💻 LocalNitro1.547s (+0.6%)2.009s (~)0.013s (+3.7%)2.025s (~)0.479s301.00x
🐘 PostgresNext.js (Turbopack)1.558s (-3.7%)2.009s (~)0.005s (+2.6%)2.026s (~)0.467s301.01x
💻 LocalExpress1.574s (+2.7%)2.011s (~)0.013s (+0.8%)2.026s (~)0.453s301.02x
💻 LocalNext.js (Turbopack)1.584s (~)1.971s (~)0.011s (-23.6% 🟢)2.024s (~)0.441s301.03x
🐘 PostgresNitro1.597s (+0.7%)2.002s (~)0.005s (+3.4%)2.026s (~)0.428s301.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express6.367s (+16.0% 🔺)7.885s (+13.5% 🔺)0.250s (+8.7% 🔺)8.629s (+11.5% 🔺)2.261s71.00x
▲ VercelNext.js (Turbopack)10.932s (+18.7% 🔺)11.820s (+8.5% 🔺)0.243s (-3.3%)13.252s (+14.1% 🔺)2.320s51.72x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.769s (-6.4% 🟢)1.065s (~)0.000s (-32.1% 🟢)1.102s (+2.2%)0.333s551.00x
🐘 PostgresNitro0.806s (-1.8%)1.045s (-5.2% 🟢)0.000s (-36.8% 🟢)1.060s (-5.4% 🟢)0.253s571.05x
🐘 PostgresNext.js (Turbopack)1.006s (+0.8%)1.418s (-2.9%)0.000s (-100.0% 🟢)1.429s (-3.7%)0.422s431.31x
💻 LocalExpress1.240s (+0.6%)2.013s (~)0.000s (-60.0% 🟢)2.015s (~)0.776s301.61x
💻 LocalNitro1.268s (~)2.013s (~)0.000s (+14.3% 🔺)2.016s (~)0.748s301.65x
💻 LocalNext.js (Turbopack)1.354s (-2.9%)1.979s (~)0.001s (+77.8% 🔺)2.016s (~)0.662s301.76x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.701s (+27.1% 🔺)5.023s (+15.1% 🔺)0.002s (+Infinity% 🔺)5.526s (+14.0% 🔺)1.825s111.00x
▲ VercelNext.js (Turbopack)5.168s (+6.3% 🔺)6.181s (+10.6% 🔺)0.000s (NaN%)6.952s (+1.2%)1.784s91.40x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.703s (+0.7%)2.301s (~)0.000s (+Infinity% 🔺)2.316s (~)0.613s261.00x
🐘 PostgresNitro1.770s (-3.3%)2.301s (-3.2%)0.000s (-100.0% 🟢)2.332s (-3.0%)0.562s261.04x
🐘 PostgresNext.js (Turbopack)2.551s (-17.5% 🟢)3.051s (-13.6% 🟢)0.000s (-71.7% 🟢)3.059s (-13.6% 🟢)0.508s201.50x
💻 LocalNitro3.469s (+5.1% 🔺)4.095s (+1.7%)0.000s (-30.0% 🟢)4.099s (+1.7%)0.630s152.04x
💻 LocalExpress3.538s (+7.8% 🔺)4.027s (+4.9%)0.001s (+113.3% 🔺)4.030s (+4.9%)0.492s152.08x
💻 LocalNext.js (Turbopack)3.541s (~)4.058s (+1.7%)0.001s (+113.3% 🔺)4.098s (+1.4%)0.557s152.08x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.371s (+2.8%)6.997s (+7.5% 🔺)0.000s (-100.0% 🟢)7.482s (+7.0% 🔺)2.111s91.00x
▲ VercelNext.js (Turbopack)8.546s (+24.9% 🔺)8.850s (+15.0% 🔺)0.000s (-100.0% 🟢)10.343s (+19.1% 🔺)1.797s61.59x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro16/21
🐘 PostgresNext.js (Turbopack)10/21
▲ VercelExpress21/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres17/21
Next.js (Turbopack)🐘 Postgres17/21
Nitro🐘 Postgres14/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)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

Comment thread.changeset/changelog.mjs Outdated
…tten summary verbatim, leaking injected `pr:`/`commit:` directive lines into the changelog so the entry title becomes literally `pr: #N`.
This commit fixes the issue reported at .changeset/changelog.mjs:368
## Bug
`maybeRewriteChangesetForBackport` prepends directive lines to a backport changeset's summary:
```js
const rewrittenSummary =
`pr: #${originalPRNumber}\n` +
`commit: ${changeset.commit}\n` +
summary;
return { ...changeset, summary: rewrittenSummary };
```
The upstream `@changesets/changelog-github` `getReleaseLine` consumes those directives and **strips them** from the summary (via `.replace(/^\s*(?:pr|pull|pull\s+request):.../im, ...)`, `commit:`, `author:` etc.) before splitting into `firstLine`/`futureLines` and rendering. So in the normal path the directives never appear in the output — that's the whole point of injecting them.
The offline fallback, however, does **not** strip them:
```js
function offlineReleaseLine(changeset, options) {
const [firstLine, ...futureLines] = (changeset.summary ?? '')
.split('\n')
.map((l) => l.trimEnd());
...
}
```
It just splits on `\n` and renders verbatim.
### Concrete trigger
A backport changeset where:
1. `maybeRewriteChangesetForBackport` **succeeds** (network up, PR resolved, directives injected → `rewritten.summary` starts with `pr: #N\ncommit: <sha>\n`), and
2. the subsequent `limitUpstream(() => upstream.getReleaseLine(rewritten, ...))` **throws** — exactly the flaky/oversized GraphQL batch scenario this fallback was added for.
The catch block then ran `offlineReleaseLine(rewritten, options)`, producing:
```
- [`abc1234`](...) - pr: #123
commit: abc1234...
<real first line>
```
The entry title becomes literally `pr: #123` and `commit: …` leaks as body text.
## Fix
Pass the **original** `changeset` to `offlineReleaseLine` instead of `rewritten`. The backport rewrite only mutates `summary`; `changeset.commit` is untouched, so the offline commit link is identical, while the original summary has no injected directives. The offline path already drops PR/author attribution it can't resolve, so using the un-rewritten summary is exactly correct.
The dependency-line path (`offlineDependencyReleaseLine`) is unaffected since no directive injection happens there.
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: VaguelySerious <mittgfu@gmail.com>

@pranaygppranaygp 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.

Reviewed the diff and verified the core mechanism end to end against the real file (stubbed upstream, Node 22.18, require()-able ESM confirmed):

  • Limiter works as claimed: 120 getReleaseLine calls → max 25 concurrent → ~5 DataLoader batches of ≤25, order preserved. Since get-github-info's DataLoader is a module-level singleton built with no maxBatchSize (confirmed in get-github-info@0.7.0 src) and each getReleaseLine fires exactly one .load(), gating concurrency at 25 genuinely bounds the per-batch query size while preserving full PR/author attribution. Good, well-reasoned fix.
  • Offline getReleaseLine mirrors the upstream template exactly (changelog-github@0.5.2): `\n\n-${prefix ? ` ${prefix} -` : ''} ${firstLine}...`, dropping only the pull/Thanks attribution. The 2nd-commit fix (feed the original changeset, not rewritten) is correct and I confirmed it no longer leaks pr:/commit: directives.
  • Offline dependency line is lossless — dep lines only ever render commit links, which you reconstruct from the SHA, so that path loses nothing even when it falls back.

LGTM to ship — it unblocks the release. A few non-blocking notes inline (one substantive: the dependency-rollup path isn't actually batch-bounded by the limiter and is carried entirely by the fallback).

// truncated/gzip-broken body (which `node-fetch@2` surfaces as
// `ERR_STREAM_PREMATURE_CLOSE`) or a 502, aborting the whole release.
// 6. Wraps the upstream delegation in a try/catch with a network-free
// fallback line, so a transient failure on one batch degrades a single

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.

Minor wording: "degrades a single entry" undersells it. When an oversized getReleaseLine batch fails, every .load() sharing that one fetch rejects together, so a whole batch (~UPSTREAM_CONCURRENCY, up to 25 entries) drops to the offline line at once — not one. The important half ("instead of failing the release") still holds; just the count is off.

options,
);
try {
return await limitUpstream(() =>

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.

The concurrency gate bounds the getReleaseLine batch nicely, but it does not bound this path's batch. apply-release-plan's getChangelogEntry calls getDependencyReleaseLine once per release with relevantChangesets (every changeset that bumped that package's deps), and upstream fans out to one getInfo() per changeset inside a single internal Promise.all — so all of those .load()s land in one DataLoader batch within this one limiter slot. A package whose deps were bumped by hundreds of changesets (e.g. the many packages depending on @workflow/core) therefore still produces one oversized query here, exactly the shape this PR is fixing.

In practice the release stays safe: that query throws → catchofflineDependencyReleaseLine, which is lossless for dependency lines (they only render commit links). So no functional gap. But point 5's rationale — "splits its lookups across several small GraphQL queries" — only really applies to getReleaseLine; this path is carried by the fallback, not by batch-splitting. Worth a word in the comments so the next reader doesn't assume both paths are bounded (or, if you want it truly bounded, gate the per-getInfo fan-out rather than the outer call).

// the changelog (the entry title would become literally `pr: #N`). The
// rewrite only touches `summary`; `changeset.commit` is unchanged, so the
// offline commit link is identical either way.
return offlineReleaseLine(changeset, options);

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.

Good fix on the backport-rewrite leak. One residual of the same class: if a changeset's summary manually contains a pr:/commit:/author: directive, maybeRewriteChangesetForBackport returns it unchanged (the guard at L309), upstream would strip it before rendering — but the offline path renders it verbatim, so the entry title becomes literally pr: #123 (confirmed locally). Much rarer than the backport case (needs a hand-written directive and an upstream failure), so not a blocker. If you want the offline path to fully match upstream, strip the leading directive lines here the same way upstream's .replace(/^\s*(?:pr|pull|...|author|user):/im, '') chain does.

: ` \`${short}\``;
}

function offlineReleaseLine(changeset, options) {

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.

Optional: these offline helpers + the limiter are pure and easy to unit-test, and the directive leak already regressed once (needed a follow-up commit), which is a good signal this file is edit-fragile. A tiny test — force upstream to throw and assert the offline line keeps the commit link + a clean title, plus assert 120 calls never exceed 25 concurrent — would be cheap regression insurance. No test runner is wired for .changeset/ today, so low priority, but worth considering.

@VaguelySerious
VaguelySerious merged commit 7f04d87 into mainJun 26, 2026
112 of 113 checks passed
@VaguelySerious
VaguelySerious deleted the peter/fix-release-changelog-premature-close branch June 26, 2026 21:42
github-actionsBot added a commit that referenced this pull request Jun 26, 2026
…h size (#2669)
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #2671. (backport job run)

VaguelySerious added a commit that referenced this pull request Jun 26, 2026
…h size (#2669) (#2671)
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
pranaygp added a commit that referenced this pull request Jun 26, 2026
…edrive
* origin/main: (90 commits)
[ai] Fix "missing text part" stream crash by repairing UI message framing (#2537)
[ci] Fix release "Premature close" by bounding changelog GraphQL batch size (#2669)
[ai] Drop orphan UI chunks after negative-index reconnect (#2082)
Fix trace detail panel Input/Output flicker (selection-driven state machine) (#2637)
[world-local] Scope untagged startup recovery to untagged runs (#2667)
fix(world-postgres): defer loopback worker startup (#2657)
docs: deprecate DurableAgent and WorkflowChatTransport, point to AI SDK WorkflowAgent (#2616)
[web] Fix HTTP/2 in bundled server build so observability reads work (#2632)
[core] Fix turbo-mode step-body writes racing run_started (#2629)
fix(world-postgres): rename setup command (#2644)
chore: ignore workflow swc caches (#2640)
fix(ai): ignore intentional stream aborts (#2635)
fix(nitro): reload steps during Vite HMR (#2572)
[world] Make @workflow/world own its Zod dependency (#2622)
feat(web-shared): extend cn with custom tailwind-merge class groups (#2619)
[world-vercel] Enable HTTP/2 for the events API and stream writes (#2573)
Remove shadow from off-screen marker indicator button (#2614)
Fix discovery of dotted JS workflow imports (#2594)
Version Packages (beta) (#2597)
Thread occurredAt into web-shared entities (#2608)
...
# Conflicts:
#	packages/core/src/runtime/step-executor.ts
#	packages/world-vercel/src/http-client.ts
Sign up for freeto 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.

2 participants

@VaguelySerious@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [ci] Fix release "Premature close" by bounding changelog GraphQL batch size by VaguelySerious · Pull Request #2669 · vercel/workflow · GitHub
Skip to content

[ci] Fix release "Premature close" by bounding changelog GraphQL batch size - #2669

Merged
VaguelySerious merged 2 commits into
mainfrom
peter/fix-release-changelog-premature-close
Jun 26, 2026
Merged

[ci] Fix release "Premature close" by bounding changelog GraphQL batch size#2669
VaguelySerious merged 2 commits into
mainfrom
peter/fix-release-changelog-premature-close

Conversation

@VaguelySerious

Copy link
Copy Markdown
Member

Problem

The Release workflow's pnpm ci:version (changeset version) step has failed on the last ~10 runs (e.g. run 28264162169) with:

🦋 error FetchError: Invalid response body while trying to fetch https://api.github.com/graphql: Premature close
at Gunzip.<anonymous> (.../node-fetch@2.7.0/.../lib/index.js:217:52)
code: 'ERR_STREAM_PREMATURE_CLOSE'

Root cause

@changesets/changelog-github resolves each changelog entry via @changesets/get-github-info, whose DataLoader is constructed with no maxBatchSize. @changesets/apply-release-plan calls getReleaseLine for every changeset inside one Promise.all, so all commits fold into a single GraphQL query.

We currently have ~300 changesets accumulated (beta pre mode, not yet flushed). That one query grows past what GitHub's GraphQL endpoint will reliably serve. Reproduced against the live API with the exact query shape on Node 24 + node-fetch@2.7.0:

commits in one queryresult
50–290200 OK (~120KB gzip)
300flaky: 200 or broken body / invalid json "<html>…"
350502 Bad Gateway

GitHub returns a truncated/gzip-broken body — which node-fetch@2 surfaces as ERR_STREAM_PREMATURE_CLOSE — or a 502, aborting the whole release. It's deterministic now because the backlog is reliably past the threshold, and it only grows.

The custom .changeset/changelog.mjs wrapper already intends to "never want a flaky network call to break pnpm changeset version", but only guarded its own backport lookups — not the upstream.getReleaseLine delegation that runs node-fetch.

Fix

  1. Bound the batch size via a concurrency gate (25). Limiting how many upstream calls are in flight bounds the number of get-github-info.load() calls per tick, which bounds the DataLoader batch — so the work splits into several small queries GitHub serves reliably, preserving full PR/author attribution.
  2. Wrap the upstream delegation in try/catch with a network-free fallback line, so a transient failure on a single batch degrades one entry instead of failing the release (finally extends the wrapper's stated promise to the delegation path).

Validation (empirical, against the live GitHub API)

  • Ungated → 1 request of 120; gated p-limit(25)5 requests, max batch 25.
  • Through the real modified wrapper over 120 commits: 5 requests, max batch 25, all 120 lines retain their [\sha`](…)commit link +Thanks @…` attribution.
  • Forcing node-fetch to throw ERR_STREAM_PREMATURE_CLOSE: no throw, well-formed offline line with commit link + multi-line summary.

🤖 Generated with Claude Code

…h size
The Release workflow's `changeset version` step has failed on the last ~10
runs with `FetchError: ... api.github.com/graphql: Premature close`.
`@changesets/changelog-github` resolves each entry via
`@changesets/get-github-info`, whose DataLoader is created with no
`maxBatchSize`. `apply-release-plan` calls `getReleaseLine` for every
changeset in one `Promise.all`, so all commits fold into a single GraphQL
query. With ~300 changesets accumulated in beta pre mode, that query grows
past what GitHub's GraphQL endpoint reliably serves — it returns a
truncated/gzip-broken body (which node-fetch@2 surfaces as
ERR_STREAM_PREMATURE_CLOSE) or a 502, aborting the whole release.
The custom `.changeset/changelog.mjs` wrapper already aimed to never let a
network call break the release, but only guarded its own backport lookups,
not the upstream delegation that runs node-fetch.
Fix:
- Cap concurrent upstream calls (25) so the DataLoader batches split across
several small queries GitHub can serve, preserving full PR/author
attribution. Verified against the live API: 120 commits -> 5 requests of
<=25 instead of one batch of 120.
- Wrap the upstream delegation in try/catch with a network-free fallback
line, so a transient failure on one batch degrades one entry instead of
failing the release.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code ownersJune 26, 2026 21:09
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fcd41ca

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

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

vercelBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production144202301672
✅ 💻 Local Development160502191824
✅ 📦 Local Production160502191824
✅ 🐘 Local Postgres159302311824
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total7282010788360

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-turbopack14903
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable15101
✅ nitro-stable126026
✅ nuxt-stable126026
✅ sveltekit-stable14507
✅ vite-stable126026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable127025
✅ e2e-local-dev-tanstack-start-127025
✅ e2e-local-postgres-nest-stable126026
✅ e2e-local-postgres-tanstack-start-126026
✅ e2e-local-prod-nest-stable127025
✅ e2e-local-prod-tanstack-start-127025
✅ e2e-vercel-prod-tanstack-start125027

📋 View full workflow run

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.048s (+3.7%)1.006s (~)0.958s101.00x
💻 LocalNitro0.048s (+8.5% 🔺)1.006s (~)0.958s101.00x
💻 LocalNext.js (Turbopack)0.049s (~)1.006s (~)0.956s101.03x
🐘 PostgresNext.js (Turbopack)0.055s (-9.1% 🟢)1.011s (~)0.957s101.14x
🐘 PostgresExpress0.065s (-13.6% 🟢)1.012s (~)0.947s101.35x
🐘 PostgresNitro0.072s (~)1.013s (~)0.941s101.49x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.204s (-27.8% 🟢)1.652s (-13.6% 🟢)1.448s101.00x
▲ VercelNext.js (Turbopack)0.548s (-10.5% 🟢)2.528s (~)1.980s102.69x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.082s (~)2.006s (~)0.924s101.00x
💻 LocalExpress1.083s (+0.7%)2.007s (~)0.924s101.00x
🐘 PostgresNext.js (Turbopack)1.085s (-0.9%)2.010s (~)0.926s101.00x
💻 LocalNext.js (Turbopack)1.085s (+0.5%)2.006s (~)0.921s101.00x
🐘 PostgresExpress1.095s (~)2.010s (~)0.915s101.01x
🐘 PostgresNitro1.102s (+0.5%)2.010s (~)0.908s101.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.393s (+2.1%)3.035s (+0.9%)1.641s101.00x
▲ VercelNext.js (Turbopack)2.053s (-12.7% 🟢)3.736s (-0.7%)1.682s101.47x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.446s (~)11.022s (~)0.576s31.00x
💻 LocalExpress10.459s (~)11.024s (~)0.565s31.00x
🐘 PostgresNext.js (Turbopack)10.465s (-0.6%)11.015s (~)0.549s31.00x
💻 LocalNext.js (Turbopack)10.469s (~)11.023s (~)0.554s31.00x
🐘 PostgresNitro10.492s (~)11.018s (~)0.526s31.00x
🐘 PostgresExpress10.502s (~)11.020s (~)0.518s31.01x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express12.061s (+2.7%)14.083s (+4.4%)2.022s31.00x
▲ VercelNext.js (Turbopack)13.273s (+4.5%)15.530s (+5.7% 🔺)2.258s21.10x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)13.578s (~)14.027s (~)0.449s51.00x
💻 LocalNitro13.613s (~)14.027s (~)0.414s51.00x
💻 LocalExpress13.631s (+0.5%)14.027s (~)0.397s51.00x
🐘 PostgresNitro13.683s (~)14.020s (~)0.337s51.01x
🐘 PostgresExpress13.766s (+0.6%)14.016s (~)0.250s51.01x
🐘 PostgresNext.js (Turbopack)13.783s (~)14.219s (+1.4%)0.436s51.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express18.000s (+9.3% 🔺)20.026s (+9.4% 🔺)2.026s41.00x
▲ VercelNext.js (Turbopack)18.839s (+6.9% 🔺)20.609s (+6.5% 🔺)1.770s31.05x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express12.073s (-3.0%)12.875s (-1.2%)0.802s71.00x
💻 LocalNitro12.126s (+0.9%)13.026s (+3.0%)0.901s71.00x
💻 LocalNext.js (Turbopack)12.134s (-3.1%)13.025s (-1.1%)0.892s71.01x
💻 LocalExpress12.203s (+1.6%)13.027s (+6.1% 🔺)0.824s71.01x
🐘 PostgresNext.js (Turbopack)12.229s (-2.2%)13.017s (~)0.788s71.01x
🐘 PostgresNitro12.302s (-0.9%)13.022s (~)0.720s71.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express19.662s (+7.8% 🔺)21.742s (+8.4% 🔺)2.080s51.00x
▲ VercelNext.js (Turbopack)21.512s (+1.3%)23.389s (+1.6%)1.877s41.09x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.166s (-0.8%)2.008s (~)0.842s151.00x
🐘 PostgresNitro1.182s (-0.6%)2.008s (~)0.825s151.01x
🐘 PostgresNext.js (Turbopack)1.191s (-1.6%)2.007s (-3.3%)0.816s151.02x
💻 LocalNitro1.400s (+1.8%)2.006s (~)0.606s151.20x
💻 LocalExpress1.421s (+1.9%)2.007s (~)0.586s151.22x
💻 LocalNext.js (Turbopack)1.472s (+4.7%)2.007s (~)0.535s151.26x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.561s (+18.3% 🔺)4.068s (+5.3% 🔺)1.508s81.00x
▲ VercelNext.js (Turbopack)4.041s (+25.4% 🔺)5.752s (+17.5% 🔺)1.711s61.58x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.300s (-2.4%)3.008s (~)1.708s101.00x
🐘 PostgresExpress1.331s (-1.3%)2.317s (-7.7% 🟢)0.985s131.02x
🐘 PostgresNitro1.354s (+2.3%)2.394s (-12.5% 🟢)1.040s131.04x
💻 LocalNitro2.447s (-0.7%)2.918s (-3.0%)0.470s111.88x
💻 LocalExpress2.781s (+12.0% 🔺)3.109s (+3.3%)0.328s102.14x
💻 LocalNext.js (Turbopack)2.807s (+16.9% 🔺)3.109s (+3.3%)0.302s102.16x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.787s (+10.9% 🔺)4.403s (+8.4% 🔺)1.616s71.00x
▲ VercelNext.js (Turbopack)3.872s (+8.7% 🔺)6.078s (+16.8% 🔺)2.207s51.39x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.612s (~)4.012s (-5.9% 🟢)2.399s81.00x
🐘 PostgresNitro1.670s (~)4.441s (-6.1% 🟢)2.770s71.04x
🐘 PostgresNext.js (Turbopack)2.288s (-29.4% 🟢)5.182s (-13.9% 🟢)2.894s61.42x
💻 LocalNitro6.144s (-1.0%)6.816s (-2.8%)0.672s53.81x
💻 LocalExpress6.790s (+13.7% 🔺)7.416s (+15.6% 🔺)0.626s54.21x
💻 LocalNext.js (Turbopack)6.878s (+15.4% 🔺)8.268s (+14.6% 🔺)1.391s44.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express4.610s (+67.5% 🔺)6.681s (+53.2% 🔺)2.070s51.00x
▲ VercelNext.js (Turbopack)5.407s (+22.7% 🔺)7.280s (+16.6% 🔺)1.873s51.17x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.177s (~)2.008s (~)0.831s151.00x
🐘 PostgresNitro1.189s (+0.6%)2.008s (~)0.819s151.01x
🐘 PostgresNext.js (Turbopack)1.195s (~)2.006s (~)0.812s151.02x
💻 LocalNitro1.415s (+1.2%)2.007s (~)0.592s151.20x
💻 LocalExpress1.416s (+1.4%)2.007s (~)0.590s151.20x
💻 LocalNext.js (Turbopack)1.443s (~)2.006s (~)0.563s151.23x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.246s (+8.3% 🔺)3.921s (+6.6% 🔺)1.674s81.00x
▲ VercelNext.js (Turbopack)3.820s (+19.2% 🔺)5.341s (+10.0% 🔺)1.521s61.70x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.291s (-3.8%)2.316s (+0.9%)1.025s131.00x
🐘 PostgresNext.js (Turbopack)1.294s (-1.6%)3.010s (~)1.716s101.00x
🐘 PostgresNitro1.347s (+4.2%)2.317s (~)0.970s131.04x
💻 LocalNitro2.539s (+5.3% 🔺)3.009s (~)0.470s101.97x
💻 LocalExpress2.694s (+11.9% 🔺)3.009s (~)0.315s102.09x
💻 LocalNext.js (Turbopack)2.710s (+2.7%)3.010s (-3.2%)0.300s102.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.881s (+16.6% 🔺)4.624s (+20.8% 🔺)1.743s71.00x
▲ VercelNext.js (Turbopack)4.760s (+39.8% 🔺)6.645s (+38.4% 🔺)1.886s51.65x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.637s (+0.5%)4.447s (+10.9% 🔺)2.811s71.00x
🐘 PostgresNitro1.879s (+14.0% 🔺)4.870s (+17.6% 🔺)2.991s71.15x
🐘 PostgresNext.js (Turbopack)3.326s (+4.1%)6.215s (~)2.889s52.03x
💻 LocalNext.js (Turbopack)6.042s (+7.0% 🔺)6.618s (+6.5% 🔺)0.575s53.69x
💻 LocalExpress6.361s (+14.7% 🔺)7.017s (+12.9% 🔺)0.655s53.89x
💻 LocalNitro7.257s (+22.0% 🔺)8.020s (+24.9% 🔺)0.763s44.43x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.768s (+38.5% 🔺)5.671s (+29.3% 🔺)1.903s61.00x
▲ VercelNext.js (Turbopack)4.935s (-8.6% 🟢)6.674s (-6.1% 🟢)1.738s51.31x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.511s (-17.2% 🟢)1.006s (-1.9%)0.495s601.00x
🐘 PostgresExpress0.551s (-5.4% 🟢)1.023s (-1.8%)0.472s591.08x
🐘 PostgresNitro0.584s (+1.0%)1.006s (-1.6%)0.422s601.14x
💻 LocalNitro0.585s (~)1.022s (~)0.437s591.15x
💻 LocalExpress0.590s (+5.8% 🔺)1.005s (~)0.416s601.15x
💻 LocalNext.js (Turbopack)0.611s (+2.0%)1.022s (+1.7%)0.411s591.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.182s (+29.4% 🔺)5.219s (+35.4% 🔺)2.037s121.00x
▲ VercelNext.js (Turbopack)4.964s (+32.2% 🔺)6.751s (+30.3% 🔺)1.786s91.56x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.229s (-11.6% 🟢)2.007s (~)0.778s451.00x
🐘 PostgresExpress1.345s (-3.1%)2.053s (~)0.708s441.09x
🐘 PostgresNitro1.412s (+7.7% 🔺)2.054s (+2.3%)0.642s441.15x
💻 LocalNitro1.478s (+3.6%)2.006s (~)0.528s451.20x
💻 LocalExpress1.492s (+3.0%)2.006s (-1.1%)0.514s451.21x
💻 LocalNext.js (Turbopack)1.498s (~)2.006s (~)0.508s451.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express8.097s (+38.5% 🔺)10.089s (+35.8% 🔺)1.992s91.00x
▲ VercelNext.js (Turbopack)9.498s (+12.5% 🔺)11.514s (+13.2% 🔺)2.017s81.17x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)2.413s (-13.6% 🟢)3.008s (-3.4%)0.594s401.00x
🐘 PostgresExpress2.517s (-7.9% 🟢)3.034s (-1.7%)0.517s401.04x
🐘 PostgresNitro2.726s (+0.7%)3.086s (-1.6%)0.360s391.13x
💻 LocalNitro3.138s (-2.1%)3.913s (+2.4%)0.775s311.30x
💻 LocalNext.js (Turbopack)3.225s (-1.7%)3.978s (-0.8%)0.753s311.34x
💻 LocalExpress3.300s (+4.4%)4.010s (+5.8% 🔺)0.710s301.37x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express18.146s (+52.1% 🔺)20.493s (+46.9% 🔺)2.347s71.00x
▲ VercelNext.js (Turbopack)22.977s (+21.7% 🔺)24.987s (+19.6% 🔺)2.011s51.27x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.191s (+1.6%)1.023s (+1.5%)0.832s591.00x
🐘 PostgresExpress0.201s (-9.8% 🟢)1.006s (~)0.805s601.05x
🐘 PostgresNitro0.214s (-1.8%)1.006s (~)0.792s601.12x
💻 LocalNitro0.462s (+7.7% 🔺)1.005s (~)0.543s602.41x
💻 LocalExpress0.483s (+13.1% 🔺)1.005s (~)0.522s602.52x
💻 LocalNext.js (Turbopack)0.628s (+6.0% 🔺)1.022s (+1.7%)0.394s593.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.370s (+19.6% 🔺)2.945s (+15.5% 🔺)1.575s211.00x
▲ VercelNext.js (Turbopack)2.815s (+36.1% 🔺)4.511s (+21.4% 🔺)1.696s142.05x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.270s (-9.4% 🟢)1.006s (-1.2%)0.736s901.00x
🐘 PostgresExpress0.318s (-5.9% 🟢)1.006s (-1.1%)0.688s901.18x
🐘 PostgresNitro0.345s (+1.2%)1.007s (~)0.662s901.28x
💻 LocalNitro2.079s (-4.1%)2.686s (-4.0%)0.607s347.71x
💻 LocalExpress2.164s (+2.0%)2.767s (+1.1%)0.603s338.02x
💻 LocalNext.js (Turbopack)2.792s (+7.0% 🔺)3.258s (+4.6%)0.466s2810.35x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.990s (+36.3% 🔺)3.997s (+30.3% 🔺)2.006s231.00x
▲ VercelNext.js (Turbopack)3.585s (+23.3% 🔺)5.408s (+12.6% 🔺)1.823s171.80x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.478s (-9.6% 🟢)3.010s (-1.7%)2.532s401.00x
🐘 PostgresExpress0.532s (-2.6%)1.149s (+3.7%)0.618s1051.11x
🐘 PostgresNitro0.545s (-3.0%)1.050s (-3.5%)0.505s1151.14x
💻 LocalNitro10.004s (+6.8% 🔺)11.031s (+5.6% 🔺)1.027s1120.93x
💻 LocalNext.js (Turbopack)10.057s (+6.1% 🔺)11.211s (+6.5% 🔺)1.155s1121.04x
💻 LocalExpress10.417s (+13.4% 🔺)11.305s (+9.1% 🔺)0.887s1121.79x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.916s (+52.4% 🔺)4.704s (+22.2% 🔺)1.788s261.00x
▲ VercelNext.js (Turbopack)5.493s (+46.5% 🔺)7.471s (+35.5% 🔺)1.979s171.88x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.137s (-1.3%)1.969s (~)0.012s (+17.0% 🔺)2.020s (~)0.883s101.00x
🐘 PostgresExpress1.145s (-1.1%)2.000s (~)0.001s (-21.4% 🟢)2.010s (~)0.865s101.01x
🐘 PostgresNext.js (Turbopack)1.153s (-1.0%)2.000s (~)0.001s (+30.0% 🔺)2.010s (~)0.857s101.01x
💻 LocalNitro1.160s (+0.8%)2.004s (~)0.012s (+9.7% 🔺)2.020s (~)0.859s101.02x
💻 LocalExpress1.164s (+1.1%)2.005s (~)0.012s (+22.8% 🔺)2.021s (~)0.856s101.02x
🐘 PostgresNitro1.166s (-2.1%)1.999s (~)0.001s (-26.7% 🟢)2.011s (~)0.845s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.974s (-7.9% 🟢)3.569s (+9.0% 🔺)2.038s (+21.1% 🔺)6.129s (+12.0% 🔺)4.155s101.00x
▲ VercelNext.js (Turbopack)3.986s (+28.8% 🔺)4.016s (+6.1% 🔺)1.953s (+42.3% 🔺)7.748s (+23.1% 🔺)3.761s102.02x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.540s (-0.8%)2.003s (~)0.005s (+4.7%)2.026s (~)0.486s301.00x
💻 LocalNitro1.547s (+0.6%)2.009s (~)0.013s (+3.7%)2.025s (~)0.479s301.00x
🐘 PostgresNext.js (Turbopack)1.558s (-3.7%)2.009s (~)0.005s (+2.6%)2.026s (~)0.467s301.01x
💻 LocalExpress1.574s (+2.7%)2.011s (~)0.013s (+0.8%)2.026s (~)0.453s301.02x
💻 LocalNext.js (Turbopack)1.584s (~)1.971s (~)0.011s (-23.6% 🟢)2.024s (~)0.441s301.03x
🐘 PostgresNitro1.597s (+0.7%)2.002s (~)0.005s (+3.4%)2.026s (~)0.428s301.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express6.367s (+16.0% 🔺)7.885s (+13.5% 🔺)0.250s (+8.7% 🔺)8.629s (+11.5% 🔺)2.261s71.00x
▲ VercelNext.js (Turbopack)10.932s (+18.7% 🔺)11.820s (+8.5% 🔺)0.243s (-3.3%)13.252s (+14.1% 🔺)2.320s51.72x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.769s (-6.4% 🟢)1.065s (~)0.000s (-32.1% 🟢)1.102s (+2.2%)0.333s551.00x
🐘 PostgresNitro0.806s (-1.8%)1.045s (-5.2% 🟢)0.000s (-36.8% 🟢)1.060s (-5.4% 🟢)0.253s571.05x
🐘 PostgresNext.js (Turbopack)1.006s (+0.8%)1.418s (-2.9%)0.000s (-100.0% 🟢)1.429s (-3.7%)0.422s431.31x
💻 LocalExpress1.240s (+0.6%)2.013s (~)0.000s (-60.0% 🟢)2.015s (~)0.776s301.61x
💻 LocalNitro1.268s (~)2.013s (~)0.000s (+14.3% 🔺)2.016s (~)0.748s301.65x
💻 LocalNext.js (Turbopack)1.354s (-2.9%)1.979s (~)0.001s (+77.8% 🔺)2.016s (~)0.662s301.76x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.701s (+27.1% 🔺)5.023s (+15.1% 🔺)0.002s (+Infinity% 🔺)5.526s (+14.0% 🔺)1.825s111.00x
▲ VercelNext.js (Turbopack)5.168s (+6.3% 🔺)6.181s (+10.6% 🔺)0.000s (NaN%)6.952s (+1.2%)1.784s91.40x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.703s (+0.7%)2.301s (~)0.000s (+Infinity% 🔺)2.316s (~)0.613s261.00x
🐘 PostgresNitro1.770s (-3.3%)2.301s (-3.2%)0.000s (-100.0% 🟢)2.332s (-3.0%)0.562s261.04x
🐘 PostgresNext.js (Turbopack)2.551s (-17.5% 🟢)3.051s (-13.6% 🟢)0.000s (-71.7% 🟢)3.059s (-13.6% 🟢)0.508s201.50x
💻 LocalNitro3.469s (+5.1% 🔺)4.095s (+1.7%)0.000s (-30.0% 🟢)4.099s (+1.7%)0.630s152.04x
💻 LocalExpress3.538s (+7.8% 🔺)4.027s (+4.9%)0.001s (+113.3% 🔺)4.030s (+4.9%)0.492s152.08x
💻 LocalNext.js (Turbopack)3.541s (~)4.058s (+1.7%)0.001s (+113.3% 🔺)4.098s (+1.4%)0.557s152.08x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.371s (+2.8%)6.997s (+7.5% 🔺)0.000s (-100.0% 🟢)7.482s (+7.0% 🔺)2.111s91.00x
▲ VercelNext.js (Turbopack)8.546s (+24.9% 🔺)8.850s (+15.0% 🔺)0.000s (-100.0% 🟢)10.343s (+19.1% 🔺)1.797s61.59x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro16/21
🐘 PostgresNext.js (Turbopack)10/21
▲ VercelExpress21/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres17/21
Next.js (Turbopack)🐘 Postgres17/21
Nitro🐘 Postgres14/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)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

Comment thread.changeset/changelog.mjs Outdated
…tten summary verbatim, leaking injected `pr:`/`commit:` directive lines into the changelog so the entry title becomes literally `pr: #N`.
This commit fixes the issue reported at .changeset/changelog.mjs:368
## Bug
`maybeRewriteChangesetForBackport` prepends directive lines to a backport changeset's summary:
```js
const rewrittenSummary =
`pr: #${originalPRNumber}\n` +
`commit: ${changeset.commit}\n` +
summary;
return { ...changeset, summary: rewrittenSummary };
```
The upstream `@changesets/changelog-github` `getReleaseLine` consumes those directives and **strips them** from the summary (via `.replace(/^\s*(?:pr|pull|pull\s+request):.../im, ...)`, `commit:`, `author:` etc.) before splitting into `firstLine`/`futureLines` and rendering. So in the normal path the directives never appear in the output — that's the whole point of injecting them.
The offline fallback, however, does **not** strip them:
```js
function offlineReleaseLine(changeset, options) {
const [firstLine, ...futureLines] = (changeset.summary ?? '')
.split('\n')
.map((l) => l.trimEnd());
...
}
```
It just splits on `\n` and renders verbatim.
### Concrete trigger
A backport changeset where:
1. `maybeRewriteChangesetForBackport` **succeeds** (network up, PR resolved, directives injected → `rewritten.summary` starts with `pr: #N\ncommit: <sha>\n`), and
2. the subsequent `limitUpstream(() => upstream.getReleaseLine(rewritten, ...))` **throws** — exactly the flaky/oversized GraphQL batch scenario this fallback was added for.
The catch block then ran `offlineReleaseLine(rewritten, options)`, producing:
```
- [`abc1234`](...) - pr: #123
commit: abc1234...
<real first line>
```
The entry title becomes literally `pr: #123` and `commit: …` leaks as body text.
## Fix
Pass the **original** `changeset` to `offlineReleaseLine` instead of `rewritten`. The backport rewrite only mutates `summary`; `changeset.commit` is untouched, so the offline commit link is identical, while the original summary has no injected directives. The offline path already drops PR/author attribution it can't resolve, so using the un-rewritten summary is exactly correct.
The dependency-line path (`offlineDependencyReleaseLine`) is unaffected since no directive injection happens there.
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: VaguelySerious <mittgfu@gmail.com>

@pranaygppranaygp 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.

Reviewed the diff and verified the core mechanism end to end against the real file (stubbed upstream, Node 22.18, require()-able ESM confirmed):

  • Limiter works as claimed: 120 getReleaseLine calls → max 25 concurrent → ~5 DataLoader batches of ≤25, order preserved. Since get-github-info's DataLoader is a module-level singleton built with no maxBatchSize (confirmed in get-github-info@0.7.0 src) and each getReleaseLine fires exactly one .load(), gating concurrency at 25 genuinely bounds the per-batch query size while preserving full PR/author attribution. Good, well-reasoned fix.
  • Offline getReleaseLine mirrors the upstream template exactly (changelog-github@0.5.2): `\n\n-${prefix ? ` ${prefix} -` : ''} ${firstLine}...`, dropping only the pull/Thanks attribution. The 2nd-commit fix (feed the original changeset, not rewritten) is correct and I confirmed it no longer leaks pr:/commit: directives.
  • Offline dependency line is lossless — dep lines only ever render commit links, which you reconstruct from the SHA, so that path loses nothing even when it falls back.

LGTM to ship — it unblocks the release. A few non-blocking notes inline (one substantive: the dependency-rollup path isn't actually batch-bounded by the limiter and is carried entirely by the fallback).

// truncated/gzip-broken body (which `node-fetch@2` surfaces as
// `ERR_STREAM_PREMATURE_CLOSE`) or a 502, aborting the whole release.
// 6. Wraps the upstream delegation in a try/catch with a network-free
// fallback line, so a transient failure on one batch degrades a single

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.

Minor wording: "degrades a single entry" undersells it. When an oversized getReleaseLine batch fails, every .load() sharing that one fetch rejects together, so a whole batch (~UPSTREAM_CONCURRENCY, up to 25 entries) drops to the offline line at once — not one. The important half ("instead of failing the release") still holds; just the count is off.

options,
);
try {
return await limitUpstream(() =>

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.

The concurrency gate bounds the getReleaseLine batch nicely, but it does not bound this path's batch. apply-release-plan's getChangelogEntry calls getDependencyReleaseLine once per release with relevantChangesets (every changeset that bumped that package's deps), and upstream fans out to one getInfo() per changeset inside a single internal Promise.all — so all of those .load()s land in one DataLoader batch within this one limiter slot. A package whose deps were bumped by hundreds of changesets (e.g. the many packages depending on @workflow/core) therefore still produces one oversized query here, exactly the shape this PR is fixing.

In practice the release stays safe: that query throws → catchofflineDependencyReleaseLine, which is lossless for dependency lines (they only render commit links). So no functional gap. But point 5's rationale — "splits its lookups across several small GraphQL queries" — only really applies to getReleaseLine; this path is carried by the fallback, not by batch-splitting. Worth a word in the comments so the next reader doesn't assume both paths are bounded (or, if you want it truly bounded, gate the per-getInfo fan-out rather than the outer call).

// the changelog (the entry title would become literally `pr: #N`). The
// rewrite only touches `summary`; `changeset.commit` is unchanged, so the
// offline commit link is identical either way.
return offlineReleaseLine(changeset, options);

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.

Good fix on the backport-rewrite leak. One residual of the same class: if a changeset's summary manually contains a pr:/commit:/author: directive, maybeRewriteChangesetForBackport returns it unchanged (the guard at L309), upstream would strip it before rendering — but the offline path renders it verbatim, so the entry title becomes literally pr: #123 (confirmed locally). Much rarer than the backport case (needs a hand-written directive and an upstream failure), so not a blocker. If you want the offline path to fully match upstream, strip the leading directive lines here the same way upstream's .replace(/^\s*(?:pr|pull|...|author|user):/im, '') chain does.

: ` \`${short}\``;
}

function offlineReleaseLine(changeset, options) {

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.

Optional: these offline helpers + the limiter are pure and easy to unit-test, and the directive leak already regressed once (needed a follow-up commit), which is a good signal this file is edit-fragile. A tiny test — force upstream to throw and assert the offline line keeps the commit link + a clean title, plus assert 120 calls never exceed 25 concurrent — would be cheap regression insurance. No test runner is wired for .changeset/ today, so low priority, but worth considering.

@VaguelySerious
VaguelySerious merged commit 7f04d87 into mainJun 26, 2026
112 of 113 checks passed
@VaguelySerious
VaguelySerious deleted the peter/fix-release-changelog-premature-close branch June 26, 2026 21:42
github-actionsBot added a commit that referenced this pull request Jun 26, 2026
…h size (#2669)
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #2671. (backport job run)

VaguelySerious added a commit that referenced this pull request Jun 26, 2026
…h size (#2669) (#2671)
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
pranaygp added a commit that referenced this pull request Jun 26, 2026
…edrive
* origin/main: (90 commits)
[ai] Fix "missing text part" stream crash by repairing UI message framing (#2537)
[ci] Fix release "Premature close" by bounding changelog GraphQL batch size (#2669)
[ai] Drop orphan UI chunks after negative-index reconnect (#2082)
Fix trace detail panel Input/Output flicker (selection-driven state machine) (#2637)
[world-local] Scope untagged startup recovery to untagged runs (#2667)
fix(world-postgres): defer loopback worker startup (#2657)
docs: deprecate DurableAgent and WorkflowChatTransport, point to AI SDK WorkflowAgent (#2616)
[web] Fix HTTP/2 in bundled server build so observability reads work (#2632)
[core] Fix turbo-mode step-body writes racing run_started (#2629)
fix(world-postgres): rename setup command (#2644)
chore: ignore workflow swc caches (#2640)
fix(ai): ignore intentional stream aborts (#2635)
fix(nitro): reload steps during Vite HMR (#2572)
[world] Make @workflow/world own its Zod dependency (#2622)
feat(web-shared): extend cn with custom tailwind-merge class groups (#2619)
[world-vercel] Enable HTTP/2 for the events API and stream writes (#2573)
Remove shadow from off-screen marker indicator button (#2614)
Fix discovery of dotted JS workflow imports (#2594)
Version Packages (beta) (#2597)
Thread occurredAt into web-shared entities (#2608)
...
# Conflicts:
#	packages/core/src/runtime/step-executor.ts
#	packages/world-vercel/src/http-client.ts
Sign up for freeto 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.

2 participants

@VaguelySerious@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [ci] Fix release "Premature close" by bounding changelog GraphQL batch size by VaguelySerious · Pull Request #2669 · vercel/workflow · GitHub
Skip to content

[ci] Fix release "Premature close" by bounding changelog GraphQL batch size - #2669

Merged
VaguelySerious merged 2 commits into
mainfrom
peter/fix-release-changelog-premature-close
Jun 26, 2026
Merged

[ci] Fix release "Premature close" by bounding changelog GraphQL batch size#2669
VaguelySerious merged 2 commits into
mainfrom
peter/fix-release-changelog-premature-close

Conversation

@VaguelySerious

Copy link
Copy Markdown
Member

Problem

The Release workflow's pnpm ci:version (changeset version) step has failed on the last ~10 runs (e.g. run 28264162169) with:

🦋 error FetchError: Invalid response body while trying to fetch https://api.github.com/graphql: Premature close
at Gunzip.<anonymous> (.../node-fetch@2.7.0/.../lib/index.js:217:52)
code: 'ERR_STREAM_PREMATURE_CLOSE'

Root cause

@changesets/changelog-github resolves each changelog entry via @changesets/get-github-info, whose DataLoader is constructed with no maxBatchSize. @changesets/apply-release-plan calls getReleaseLine for every changeset inside one Promise.all, so all commits fold into a single GraphQL query.

We currently have ~300 changesets accumulated (beta pre mode, not yet flushed). That one query grows past what GitHub's GraphQL endpoint will reliably serve. Reproduced against the live API with the exact query shape on Node 24 + node-fetch@2.7.0:

commits in one queryresult
50–290200 OK (~120KB gzip)
300flaky: 200 or broken body / invalid json "<html>…"
350502 Bad Gateway

GitHub returns a truncated/gzip-broken body — which node-fetch@2 surfaces as ERR_STREAM_PREMATURE_CLOSE — or a 502, aborting the whole release. It's deterministic now because the backlog is reliably past the threshold, and it only grows.

The custom .changeset/changelog.mjs wrapper already intends to "never want a flaky network call to break pnpm changeset version", but only guarded its own backport lookups — not the upstream.getReleaseLine delegation that runs node-fetch.

Fix

  1. Bound the batch size via a concurrency gate (25). Limiting how many upstream calls are in flight bounds the number of get-github-info.load() calls per tick, which bounds the DataLoader batch — so the work splits into several small queries GitHub serves reliably, preserving full PR/author attribution.
  2. Wrap the upstream delegation in try/catch with a network-free fallback line, so a transient failure on a single batch degrades one entry instead of failing the release (finally extends the wrapper's stated promise to the delegation path).

Validation (empirical, against the live GitHub API)

  • Ungated → 1 request of 120; gated p-limit(25)5 requests, max batch 25.
  • Through the real modified wrapper over 120 commits: 5 requests, max batch 25, all 120 lines retain their [\sha`](…)commit link +Thanks @…` attribution.
  • Forcing node-fetch to throw ERR_STREAM_PREMATURE_CLOSE: no throw, well-formed offline line with commit link + multi-line summary.

🤖 Generated with Claude Code

…h size
The Release workflow's `changeset version` step has failed on the last ~10
runs with `FetchError: ... api.github.com/graphql: Premature close`.
`@changesets/changelog-github` resolves each entry via
`@changesets/get-github-info`, whose DataLoader is created with no
`maxBatchSize`. `apply-release-plan` calls `getReleaseLine` for every
changeset in one `Promise.all`, so all commits fold into a single GraphQL
query. With ~300 changesets accumulated in beta pre mode, that query grows
past what GitHub's GraphQL endpoint reliably serves — it returns a
truncated/gzip-broken body (which node-fetch@2 surfaces as
ERR_STREAM_PREMATURE_CLOSE) or a 502, aborting the whole release.
The custom `.changeset/changelog.mjs` wrapper already aimed to never let a
network call break the release, but only guarded its own backport lookups,
not the upstream delegation that runs node-fetch.
Fix:
- Cap concurrent upstream calls (25) so the DataLoader batches split across
several small queries GitHub can serve, preserving full PR/author
attribution. Verified against the live API: 120 commits -> 5 requests of
<=25 instead of one batch of 120.
- Wrap the upstream delegation in try/catch with a network-free fallback
line, so a transient failure on one batch degrades one entry instead of
failing the release.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code ownersJune 26, 2026 21:09
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fcd41ca

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

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

vercelBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production144202301672
✅ 💻 Local Development160502191824
✅ 📦 Local Production160502191824
✅ 🐘 Local Postgres159302311824
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total7282010788360

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-turbopack14903
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable15101
✅ nitro-stable126026
✅ nuxt-stable126026
✅ sveltekit-stable14507
✅ vite-stable126026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable127025
✅ e2e-local-dev-tanstack-start-127025
✅ e2e-local-postgres-nest-stable126026
✅ e2e-local-postgres-tanstack-start-126026
✅ e2e-local-prod-nest-stable127025
✅ e2e-local-prod-tanstack-start-127025
✅ e2e-vercel-prod-tanstack-start125027

📋 View full workflow run

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.048s (+3.7%)1.006s (~)0.958s101.00x
💻 LocalNitro0.048s (+8.5% 🔺)1.006s (~)0.958s101.00x
💻 LocalNext.js (Turbopack)0.049s (~)1.006s (~)0.956s101.03x
🐘 PostgresNext.js (Turbopack)0.055s (-9.1% 🟢)1.011s (~)0.957s101.14x
🐘 PostgresExpress0.065s (-13.6% 🟢)1.012s (~)0.947s101.35x
🐘 PostgresNitro0.072s (~)1.013s (~)0.941s101.49x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.204s (-27.8% 🟢)1.652s (-13.6% 🟢)1.448s101.00x
▲ VercelNext.js (Turbopack)0.548s (-10.5% 🟢)2.528s (~)1.980s102.69x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.082s (~)2.006s (~)0.924s101.00x
💻 LocalExpress1.083s (+0.7%)2.007s (~)0.924s101.00x
🐘 PostgresNext.js (Turbopack)1.085s (-0.9%)2.010s (~)0.926s101.00x
💻 LocalNext.js (Turbopack)1.085s (+0.5%)2.006s (~)0.921s101.00x
🐘 PostgresExpress1.095s (~)2.010s (~)0.915s101.01x
🐘 PostgresNitro1.102s (+0.5%)2.010s (~)0.908s101.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.393s (+2.1%)3.035s (+0.9%)1.641s101.00x
▲ VercelNext.js (Turbopack)2.053s (-12.7% 🟢)3.736s (-0.7%)1.682s101.47x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.446s (~)11.022s (~)0.576s31.00x
💻 LocalExpress10.459s (~)11.024s (~)0.565s31.00x
🐘 PostgresNext.js (Turbopack)10.465s (-0.6%)11.015s (~)0.549s31.00x
💻 LocalNext.js (Turbopack)10.469s (~)11.023s (~)0.554s31.00x
🐘 PostgresNitro10.492s (~)11.018s (~)0.526s31.00x
🐘 PostgresExpress10.502s (~)11.020s (~)0.518s31.01x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express12.061s (+2.7%)14.083s (+4.4%)2.022s31.00x
▲ VercelNext.js (Turbopack)13.273s (+4.5%)15.530s (+5.7% 🔺)2.258s21.10x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)13.578s (~)14.027s (~)0.449s51.00x
💻 LocalNitro13.613s (~)14.027s (~)0.414s51.00x
💻 LocalExpress13.631s (+0.5%)14.027s (~)0.397s51.00x
🐘 PostgresNitro13.683s (~)14.020s (~)0.337s51.01x
🐘 PostgresExpress13.766s (+0.6%)14.016s (~)0.250s51.01x
🐘 PostgresNext.js (Turbopack)13.783s (~)14.219s (+1.4%)0.436s51.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express18.000s (+9.3% 🔺)20.026s (+9.4% 🔺)2.026s41.00x
▲ VercelNext.js (Turbopack)18.839s (+6.9% 🔺)20.609s (+6.5% 🔺)1.770s31.05x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express12.073s (-3.0%)12.875s (-1.2%)0.802s71.00x
💻 LocalNitro12.126s (+0.9%)13.026s (+3.0%)0.901s71.00x
💻 LocalNext.js (Turbopack)12.134s (-3.1%)13.025s (-1.1%)0.892s71.01x
💻 LocalExpress12.203s (+1.6%)13.027s (+6.1% 🔺)0.824s71.01x
🐘 PostgresNext.js (Turbopack)12.229s (-2.2%)13.017s (~)0.788s71.01x
🐘 PostgresNitro12.302s (-0.9%)13.022s (~)0.720s71.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express19.662s (+7.8% 🔺)21.742s (+8.4% 🔺)2.080s51.00x
▲ VercelNext.js (Turbopack)21.512s (+1.3%)23.389s (+1.6%)1.877s41.09x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.166s (-0.8%)2.008s (~)0.842s151.00x
🐘 PostgresNitro1.182s (-0.6%)2.008s (~)0.825s151.01x
🐘 PostgresNext.js (Turbopack)1.191s (-1.6%)2.007s (-3.3%)0.816s151.02x
💻 LocalNitro1.400s (+1.8%)2.006s (~)0.606s151.20x
💻 LocalExpress1.421s (+1.9%)2.007s (~)0.586s151.22x
💻 LocalNext.js (Turbopack)1.472s (+4.7%)2.007s (~)0.535s151.26x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.561s (+18.3% 🔺)4.068s (+5.3% 🔺)1.508s81.00x
▲ VercelNext.js (Turbopack)4.041s (+25.4% 🔺)5.752s (+17.5% 🔺)1.711s61.58x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.300s (-2.4%)3.008s (~)1.708s101.00x
🐘 PostgresExpress1.331s (-1.3%)2.317s (-7.7% 🟢)0.985s131.02x
🐘 PostgresNitro1.354s (+2.3%)2.394s (-12.5% 🟢)1.040s131.04x
💻 LocalNitro2.447s (-0.7%)2.918s (-3.0%)0.470s111.88x
💻 LocalExpress2.781s (+12.0% 🔺)3.109s (+3.3%)0.328s102.14x
💻 LocalNext.js (Turbopack)2.807s (+16.9% 🔺)3.109s (+3.3%)0.302s102.16x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.787s (+10.9% 🔺)4.403s (+8.4% 🔺)1.616s71.00x
▲ VercelNext.js (Turbopack)3.872s (+8.7% 🔺)6.078s (+16.8% 🔺)2.207s51.39x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.612s (~)4.012s (-5.9% 🟢)2.399s81.00x
🐘 PostgresNitro1.670s (~)4.441s (-6.1% 🟢)2.770s71.04x
🐘 PostgresNext.js (Turbopack)2.288s (-29.4% 🟢)5.182s (-13.9% 🟢)2.894s61.42x
💻 LocalNitro6.144s (-1.0%)6.816s (-2.8%)0.672s53.81x
💻 LocalExpress6.790s (+13.7% 🔺)7.416s (+15.6% 🔺)0.626s54.21x
💻 LocalNext.js (Turbopack)6.878s (+15.4% 🔺)8.268s (+14.6% 🔺)1.391s44.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express4.610s (+67.5% 🔺)6.681s (+53.2% 🔺)2.070s51.00x
▲ VercelNext.js (Turbopack)5.407s (+22.7% 🔺)7.280s (+16.6% 🔺)1.873s51.17x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.177s (~)2.008s (~)0.831s151.00x
🐘 PostgresNitro1.189s (+0.6%)2.008s (~)0.819s151.01x
🐘 PostgresNext.js (Turbopack)1.195s (~)2.006s (~)0.812s151.02x
💻 LocalNitro1.415s (+1.2%)2.007s (~)0.592s151.20x
💻 LocalExpress1.416s (+1.4%)2.007s (~)0.590s151.20x
💻 LocalNext.js (Turbopack)1.443s (~)2.006s (~)0.563s151.23x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.246s (+8.3% 🔺)3.921s (+6.6% 🔺)1.674s81.00x
▲ VercelNext.js (Turbopack)3.820s (+19.2% 🔺)5.341s (+10.0% 🔺)1.521s61.70x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.291s (-3.8%)2.316s (+0.9%)1.025s131.00x
🐘 PostgresNext.js (Turbopack)1.294s (-1.6%)3.010s (~)1.716s101.00x
🐘 PostgresNitro1.347s (+4.2%)2.317s (~)0.970s131.04x
💻 LocalNitro2.539s (+5.3% 🔺)3.009s (~)0.470s101.97x
💻 LocalExpress2.694s (+11.9% 🔺)3.009s (~)0.315s102.09x
💻 LocalNext.js (Turbopack)2.710s (+2.7%)3.010s (-3.2%)0.300s102.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.881s (+16.6% 🔺)4.624s (+20.8% 🔺)1.743s71.00x
▲ VercelNext.js (Turbopack)4.760s (+39.8% 🔺)6.645s (+38.4% 🔺)1.886s51.65x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.637s (+0.5%)4.447s (+10.9% 🔺)2.811s71.00x
🐘 PostgresNitro1.879s (+14.0% 🔺)4.870s (+17.6% 🔺)2.991s71.15x
🐘 PostgresNext.js (Turbopack)3.326s (+4.1%)6.215s (~)2.889s52.03x
💻 LocalNext.js (Turbopack)6.042s (+7.0% 🔺)6.618s (+6.5% 🔺)0.575s53.69x
💻 LocalExpress6.361s (+14.7% 🔺)7.017s (+12.9% 🔺)0.655s53.89x
💻 LocalNitro7.257s (+22.0% 🔺)8.020s (+24.9% 🔺)0.763s44.43x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.768s (+38.5% 🔺)5.671s (+29.3% 🔺)1.903s61.00x
▲ VercelNext.js (Turbopack)4.935s (-8.6% 🟢)6.674s (-6.1% 🟢)1.738s51.31x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.511s (-17.2% 🟢)1.006s (-1.9%)0.495s601.00x
🐘 PostgresExpress0.551s (-5.4% 🟢)1.023s (-1.8%)0.472s591.08x
🐘 PostgresNitro0.584s (+1.0%)1.006s (-1.6%)0.422s601.14x
💻 LocalNitro0.585s (~)1.022s (~)0.437s591.15x
💻 LocalExpress0.590s (+5.8% 🔺)1.005s (~)0.416s601.15x
💻 LocalNext.js (Turbopack)0.611s (+2.0%)1.022s (+1.7%)0.411s591.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.182s (+29.4% 🔺)5.219s (+35.4% 🔺)2.037s121.00x
▲ VercelNext.js (Turbopack)4.964s (+32.2% 🔺)6.751s (+30.3% 🔺)1.786s91.56x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.229s (-11.6% 🟢)2.007s (~)0.778s451.00x
🐘 PostgresExpress1.345s (-3.1%)2.053s (~)0.708s441.09x
🐘 PostgresNitro1.412s (+7.7% 🔺)2.054s (+2.3%)0.642s441.15x
💻 LocalNitro1.478s (+3.6%)2.006s (~)0.528s451.20x
💻 LocalExpress1.492s (+3.0%)2.006s (-1.1%)0.514s451.21x
💻 LocalNext.js (Turbopack)1.498s (~)2.006s (~)0.508s451.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express8.097s (+38.5% 🔺)10.089s (+35.8% 🔺)1.992s91.00x
▲ VercelNext.js (Turbopack)9.498s (+12.5% 🔺)11.514s (+13.2% 🔺)2.017s81.17x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)2.413s (-13.6% 🟢)3.008s (-3.4%)0.594s401.00x
🐘 PostgresExpress2.517s (-7.9% 🟢)3.034s (-1.7%)0.517s401.04x
🐘 PostgresNitro2.726s (+0.7%)3.086s (-1.6%)0.360s391.13x
💻 LocalNitro3.138s (-2.1%)3.913s (+2.4%)0.775s311.30x
💻 LocalNext.js (Turbopack)3.225s (-1.7%)3.978s (-0.8%)0.753s311.34x
💻 LocalExpress3.300s (+4.4%)4.010s (+5.8% 🔺)0.710s301.37x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express18.146s (+52.1% 🔺)20.493s (+46.9% 🔺)2.347s71.00x
▲ VercelNext.js (Turbopack)22.977s (+21.7% 🔺)24.987s (+19.6% 🔺)2.011s51.27x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.191s (+1.6%)1.023s (+1.5%)0.832s591.00x
🐘 PostgresExpress0.201s (-9.8% 🟢)1.006s (~)0.805s601.05x
🐘 PostgresNitro0.214s (-1.8%)1.006s (~)0.792s601.12x
💻 LocalNitro0.462s (+7.7% 🔺)1.005s (~)0.543s602.41x
💻 LocalExpress0.483s (+13.1% 🔺)1.005s (~)0.522s602.52x
💻 LocalNext.js (Turbopack)0.628s (+6.0% 🔺)1.022s (+1.7%)0.394s593.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.370s (+19.6% 🔺)2.945s (+15.5% 🔺)1.575s211.00x
▲ VercelNext.js (Turbopack)2.815s (+36.1% 🔺)4.511s (+21.4% 🔺)1.696s142.05x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.270s (-9.4% 🟢)1.006s (-1.2%)0.736s901.00x
🐘 PostgresExpress0.318s (-5.9% 🟢)1.006s (-1.1%)0.688s901.18x
🐘 PostgresNitro0.345s (+1.2%)1.007s (~)0.662s901.28x
💻 LocalNitro2.079s (-4.1%)2.686s (-4.0%)0.607s347.71x
💻 LocalExpress2.164s (+2.0%)2.767s (+1.1%)0.603s338.02x
💻 LocalNext.js (Turbopack)2.792s (+7.0% 🔺)3.258s (+4.6%)0.466s2810.35x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.990s (+36.3% 🔺)3.997s (+30.3% 🔺)2.006s231.00x
▲ VercelNext.js (Turbopack)3.585s (+23.3% 🔺)5.408s (+12.6% 🔺)1.823s171.80x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.478s (-9.6% 🟢)3.010s (-1.7%)2.532s401.00x
🐘 PostgresExpress0.532s (-2.6%)1.149s (+3.7%)0.618s1051.11x
🐘 PostgresNitro0.545s (-3.0%)1.050s (-3.5%)0.505s1151.14x
💻 LocalNitro10.004s (+6.8% 🔺)11.031s (+5.6% 🔺)1.027s1120.93x
💻 LocalNext.js (Turbopack)10.057s (+6.1% 🔺)11.211s (+6.5% 🔺)1.155s1121.04x
💻 LocalExpress10.417s (+13.4% 🔺)11.305s (+9.1% 🔺)0.887s1121.79x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.916s (+52.4% 🔺)4.704s (+22.2% 🔺)1.788s261.00x
▲ VercelNext.js (Turbopack)5.493s (+46.5% 🔺)7.471s (+35.5% 🔺)1.979s171.88x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.137s (-1.3%)1.969s (~)0.012s (+17.0% 🔺)2.020s (~)0.883s101.00x
🐘 PostgresExpress1.145s (-1.1%)2.000s (~)0.001s (-21.4% 🟢)2.010s (~)0.865s101.01x
🐘 PostgresNext.js (Turbopack)1.153s (-1.0%)2.000s (~)0.001s (+30.0% 🔺)2.010s (~)0.857s101.01x
💻 LocalNitro1.160s (+0.8%)2.004s (~)0.012s (+9.7% 🔺)2.020s (~)0.859s101.02x
💻 LocalExpress1.164s (+1.1%)2.005s (~)0.012s (+22.8% 🔺)2.021s (~)0.856s101.02x
🐘 PostgresNitro1.166s (-2.1%)1.999s (~)0.001s (-26.7% 🟢)2.011s (~)0.845s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.974s (-7.9% 🟢)3.569s (+9.0% 🔺)2.038s (+21.1% 🔺)6.129s (+12.0% 🔺)4.155s101.00x
▲ VercelNext.js (Turbopack)3.986s (+28.8% 🔺)4.016s (+6.1% 🔺)1.953s (+42.3% 🔺)7.748s (+23.1% 🔺)3.761s102.02x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.540s (-0.8%)2.003s (~)0.005s (+4.7%)2.026s (~)0.486s301.00x
💻 LocalNitro1.547s (+0.6%)2.009s (~)0.013s (+3.7%)2.025s (~)0.479s301.00x
🐘 PostgresNext.js (Turbopack)1.558s (-3.7%)2.009s (~)0.005s (+2.6%)2.026s (~)0.467s301.01x
💻 LocalExpress1.574s (+2.7%)2.011s (~)0.013s (+0.8%)2.026s (~)0.453s301.02x
💻 LocalNext.js (Turbopack)1.584s (~)1.971s (~)0.011s (-23.6% 🟢)2.024s (~)0.441s301.03x
🐘 PostgresNitro1.597s (+0.7%)2.002s (~)0.005s (+3.4%)2.026s (~)0.428s301.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express6.367s (+16.0% 🔺)7.885s (+13.5% 🔺)0.250s (+8.7% 🔺)8.629s (+11.5% 🔺)2.261s71.00x
▲ VercelNext.js (Turbopack)10.932s (+18.7% 🔺)11.820s (+8.5% 🔺)0.243s (-3.3%)13.252s (+14.1% 🔺)2.320s51.72x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.769s (-6.4% 🟢)1.065s (~)0.000s (-32.1% 🟢)1.102s (+2.2%)0.333s551.00x
🐘 PostgresNitro0.806s (-1.8%)1.045s (-5.2% 🟢)0.000s (-36.8% 🟢)1.060s (-5.4% 🟢)0.253s571.05x
🐘 PostgresNext.js (Turbopack)1.006s (+0.8%)1.418s (-2.9%)0.000s (-100.0% 🟢)1.429s (-3.7%)0.422s431.31x
💻 LocalExpress1.240s (+0.6%)2.013s (~)0.000s (-60.0% 🟢)2.015s (~)0.776s301.61x
💻 LocalNitro1.268s (~)2.013s (~)0.000s (+14.3% 🔺)2.016s (~)0.748s301.65x
💻 LocalNext.js (Turbopack)1.354s (-2.9%)1.979s (~)0.001s (+77.8% 🔺)2.016s (~)0.662s301.76x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.701s (+27.1% 🔺)5.023s (+15.1% 🔺)0.002s (+Infinity% 🔺)5.526s (+14.0% 🔺)1.825s111.00x
▲ VercelNext.js (Turbopack)5.168s (+6.3% 🔺)6.181s (+10.6% 🔺)0.000s (NaN%)6.952s (+1.2%)1.784s91.40x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.703s (+0.7%)2.301s (~)0.000s (+Infinity% 🔺)2.316s (~)0.613s261.00x
🐘 PostgresNitro1.770s (-3.3%)2.301s (-3.2%)0.000s (-100.0% 🟢)2.332s (-3.0%)0.562s261.04x
🐘 PostgresNext.js (Turbopack)2.551s (-17.5% 🟢)3.051s (-13.6% 🟢)0.000s (-71.7% 🟢)3.059s (-13.6% 🟢)0.508s201.50x
💻 LocalNitro3.469s (+5.1% 🔺)4.095s (+1.7%)0.000s (-30.0% 🟢)4.099s (+1.7%)0.630s152.04x
💻 LocalExpress3.538s (+7.8% 🔺)4.027s (+4.9%)0.001s (+113.3% 🔺)4.030s (+4.9%)0.492s152.08x
💻 LocalNext.js (Turbopack)3.541s (~)4.058s (+1.7%)0.001s (+113.3% 🔺)4.098s (+1.4%)0.557s152.08x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.371s (+2.8%)6.997s (+7.5% 🔺)0.000s (-100.0% 🟢)7.482s (+7.0% 🔺)2.111s91.00x
▲ VercelNext.js (Turbopack)8.546s (+24.9% 🔺)8.850s (+15.0% 🔺)0.000s (-100.0% 🟢)10.343s (+19.1% 🔺)1.797s61.59x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro16/21
🐘 PostgresNext.js (Turbopack)10/21
▲ VercelExpress21/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres17/21
Next.js (Turbopack)🐘 Postgres17/21
Nitro🐘 Postgres14/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)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

Comment thread.changeset/changelog.mjs Outdated
…tten summary verbatim, leaking injected `pr:`/`commit:` directive lines into the changelog so the entry title becomes literally `pr: #N`.
This commit fixes the issue reported at .changeset/changelog.mjs:368
## Bug
`maybeRewriteChangesetForBackport` prepends directive lines to a backport changeset's summary:
```js
const rewrittenSummary =
`pr: #${originalPRNumber}\n` +
`commit: ${changeset.commit}\n` +
summary;
return { ...changeset, summary: rewrittenSummary };
```
The upstream `@changesets/changelog-github` `getReleaseLine` consumes those directives and **strips them** from the summary (via `.replace(/^\s*(?:pr|pull|pull\s+request):.../im, ...)`, `commit:`, `author:` etc.) before splitting into `firstLine`/`futureLines` and rendering. So in the normal path the directives never appear in the output — that's the whole point of injecting them.
The offline fallback, however, does **not** strip them:
```js
function offlineReleaseLine(changeset, options) {
const [firstLine, ...futureLines] = (changeset.summary ?? '')
.split('\n')
.map((l) => l.trimEnd());
...
}
```
It just splits on `\n` and renders verbatim.
### Concrete trigger
A backport changeset where:
1. `maybeRewriteChangesetForBackport` **succeeds** (network up, PR resolved, directives injected → `rewritten.summary` starts with `pr: #N\ncommit: <sha>\n`), and
2. the subsequent `limitUpstream(() => upstream.getReleaseLine(rewritten, ...))` **throws** — exactly the flaky/oversized GraphQL batch scenario this fallback was added for.
The catch block then ran `offlineReleaseLine(rewritten, options)`, producing:
```
- [`abc1234`](...) - pr: #123
commit: abc1234...
<real first line>
```
The entry title becomes literally `pr: #123` and `commit: …` leaks as body text.
## Fix
Pass the **original** `changeset` to `offlineReleaseLine` instead of `rewritten`. The backport rewrite only mutates `summary`; `changeset.commit` is untouched, so the offline commit link is identical, while the original summary has no injected directives. The offline path already drops PR/author attribution it can't resolve, so using the un-rewritten summary is exactly correct.
The dependency-line path (`offlineDependencyReleaseLine`) is unaffected since no directive injection happens there.
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: VaguelySerious <mittgfu@gmail.com>

@pranaygppranaygp 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.

Reviewed the diff and verified the core mechanism end to end against the real file (stubbed upstream, Node 22.18, require()-able ESM confirmed):

  • Limiter works as claimed: 120 getReleaseLine calls → max 25 concurrent → ~5 DataLoader batches of ≤25, order preserved. Since get-github-info's DataLoader is a module-level singleton built with no maxBatchSize (confirmed in get-github-info@0.7.0 src) and each getReleaseLine fires exactly one .load(), gating concurrency at 25 genuinely bounds the per-batch query size while preserving full PR/author attribution. Good, well-reasoned fix.
  • Offline getReleaseLine mirrors the upstream template exactly (changelog-github@0.5.2): `\n\n-${prefix ? ` ${prefix} -` : ''} ${firstLine}...`, dropping only the pull/Thanks attribution. The 2nd-commit fix (feed the original changeset, not rewritten) is correct and I confirmed it no longer leaks pr:/commit: directives.
  • Offline dependency line is lossless — dep lines only ever render commit links, which you reconstruct from the SHA, so that path loses nothing even when it falls back.

LGTM to ship — it unblocks the release. A few non-blocking notes inline (one substantive: the dependency-rollup path isn't actually batch-bounded by the limiter and is carried entirely by the fallback).

// truncated/gzip-broken body (which `node-fetch@2` surfaces as
// `ERR_STREAM_PREMATURE_CLOSE`) or a 502, aborting the whole release.
// 6. Wraps the upstream delegation in a try/catch with a network-free
// fallback line, so a transient failure on one batch degrades a single

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.

Minor wording: "degrades a single entry" undersells it. When an oversized getReleaseLine batch fails, every .load() sharing that one fetch rejects together, so a whole batch (~UPSTREAM_CONCURRENCY, up to 25 entries) drops to the offline line at once — not one. The important half ("instead of failing the release") still holds; just the count is off.

options,
);
try {
return await limitUpstream(() =>

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.

The concurrency gate bounds the getReleaseLine batch nicely, but it does not bound this path's batch. apply-release-plan's getChangelogEntry calls getDependencyReleaseLine once per release with relevantChangesets (every changeset that bumped that package's deps), and upstream fans out to one getInfo() per changeset inside a single internal Promise.all — so all of those .load()s land in one DataLoader batch within this one limiter slot. A package whose deps were bumped by hundreds of changesets (e.g. the many packages depending on @workflow/core) therefore still produces one oversized query here, exactly the shape this PR is fixing.

In practice the release stays safe: that query throws → catchofflineDependencyReleaseLine, which is lossless for dependency lines (they only render commit links). So no functional gap. But point 5's rationale — "splits its lookups across several small GraphQL queries" — only really applies to getReleaseLine; this path is carried by the fallback, not by batch-splitting. Worth a word in the comments so the next reader doesn't assume both paths are bounded (or, if you want it truly bounded, gate the per-getInfo fan-out rather than the outer call).

// the changelog (the entry title would become literally `pr: #N`). The
// rewrite only touches `summary`; `changeset.commit` is unchanged, so the
// offline commit link is identical either way.
return offlineReleaseLine(changeset, options);

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.

Good fix on the backport-rewrite leak. One residual of the same class: if a changeset's summary manually contains a pr:/commit:/author: directive, maybeRewriteChangesetForBackport returns it unchanged (the guard at L309), upstream would strip it before rendering — but the offline path renders it verbatim, so the entry title becomes literally pr: #123 (confirmed locally). Much rarer than the backport case (needs a hand-written directive and an upstream failure), so not a blocker. If you want the offline path to fully match upstream, strip the leading directive lines here the same way upstream's .replace(/^\s*(?:pr|pull|...|author|user):/im, '') chain does.

: ` \`${short}\``;
}

function offlineReleaseLine(changeset, options) {

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.

Optional: these offline helpers + the limiter are pure and easy to unit-test, and the directive leak already regressed once (needed a follow-up commit), which is a good signal this file is edit-fragile. A tiny test — force upstream to throw and assert the offline line keeps the commit link + a clean title, plus assert 120 calls never exceed 25 concurrent — would be cheap regression insurance. No test runner is wired for .changeset/ today, so low priority, but worth considering.

@VaguelySerious
VaguelySerious merged commit 7f04d87 into mainJun 26, 2026
112 of 113 checks passed
@VaguelySerious
VaguelySerious deleted the peter/fix-release-changelog-premature-close branch June 26, 2026 21:42
github-actionsBot added a commit that referenced this pull request Jun 26, 2026
…h size (#2669)
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #2671. (backport job run)

VaguelySerious added a commit that referenced this pull request Jun 26, 2026
…h size (#2669) (#2671)
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
pranaygp added a commit that referenced this pull request Jun 26, 2026
…edrive
* origin/main: (90 commits)
[ai] Fix "missing text part" stream crash by repairing UI message framing (#2537)
[ci] Fix release "Premature close" by bounding changelog GraphQL batch size (#2669)
[ai] Drop orphan UI chunks after negative-index reconnect (#2082)
Fix trace detail panel Input/Output flicker (selection-driven state machine) (#2637)
[world-local] Scope untagged startup recovery to untagged runs (#2667)
fix(world-postgres): defer loopback worker startup (#2657)
docs: deprecate DurableAgent and WorkflowChatTransport, point to AI SDK WorkflowAgent (#2616)
[web] Fix HTTP/2 in bundled server build so observability reads work (#2632)
[core] Fix turbo-mode step-body writes racing run_started (#2629)
fix(world-postgres): rename setup command (#2644)
chore: ignore workflow swc caches (#2640)
fix(ai): ignore intentional stream aborts (#2635)
fix(nitro): reload steps during Vite HMR (#2572)
[world] Make @workflow/world own its Zod dependency (#2622)
feat(web-shared): extend cn with custom tailwind-merge class groups (#2619)
[world-vercel] Enable HTTP/2 for the events API and stream writes (#2573)
Remove shadow from off-screen marker indicator button (#2614)
Fix discovery of dotted JS workflow imports (#2594)
Version Packages (beta) (#2597)
Thread occurredAt into web-shared entities (#2608)
...
# Conflicts:
#	packages/core/src/runtime/step-executor.ts
#	packages/world-vercel/src/http-client.ts
Sign up for freeto 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.

2 participants

@VaguelySerious@pranaygp
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); [ci] Fix release "Premature close" by bounding changelog GraphQL batch size by VaguelySerious · Pull Request #2669 · vercel/workflow · GitHub
Skip to content

[ci] Fix release "Premature close" by bounding changelog GraphQL batch size - #2669

Merged
VaguelySerious merged 2 commits into
mainfrom
peter/fix-release-changelog-premature-close
Jun 26, 2026
Merged

[ci] Fix release "Premature close" by bounding changelog GraphQL batch size#2669
VaguelySerious merged 2 commits into
mainfrom
peter/fix-release-changelog-premature-close

Conversation

@VaguelySerious

Copy link
Copy Markdown
Member

Problem

The Release workflow's pnpm ci:version (changeset version) step has failed on the last ~10 runs (e.g. run 28264162169) with:

🦋 error FetchError: Invalid response body while trying to fetch https://api.github.com/graphql: Premature close
at Gunzip.<anonymous> (.../node-fetch@2.7.0/.../lib/index.js:217:52)
code: 'ERR_STREAM_PREMATURE_CLOSE'

Root cause

@changesets/changelog-github resolves each changelog entry via @changesets/get-github-info, whose DataLoader is constructed with no maxBatchSize. @changesets/apply-release-plan calls getReleaseLine for every changeset inside one Promise.all, so all commits fold into a single GraphQL query.

We currently have ~300 changesets accumulated (beta pre mode, not yet flushed). That one query grows past what GitHub's GraphQL endpoint will reliably serve. Reproduced against the live API with the exact query shape on Node 24 + node-fetch@2.7.0:

commits in one queryresult
50–290200 OK (~120KB gzip)
300flaky: 200 or broken body / invalid json "<html>…"
350502 Bad Gateway

GitHub returns a truncated/gzip-broken body — which node-fetch@2 surfaces as ERR_STREAM_PREMATURE_CLOSE — or a 502, aborting the whole release. It's deterministic now because the backlog is reliably past the threshold, and it only grows.

The custom .changeset/changelog.mjs wrapper already intends to "never want a flaky network call to break pnpm changeset version", but only guarded its own backport lookups — not the upstream.getReleaseLine delegation that runs node-fetch.

Fix

  1. Bound the batch size via a concurrency gate (25). Limiting how many upstream calls are in flight bounds the number of get-github-info.load() calls per tick, which bounds the DataLoader batch — so the work splits into several small queries GitHub serves reliably, preserving full PR/author attribution.
  2. Wrap the upstream delegation in try/catch with a network-free fallback line, so a transient failure on a single batch degrades one entry instead of failing the release (finally extends the wrapper's stated promise to the delegation path).

Validation (empirical, against the live GitHub API)

  • Ungated → 1 request of 120; gated p-limit(25)5 requests, max batch 25.
  • Through the real modified wrapper over 120 commits: 5 requests, max batch 25, all 120 lines retain their [\sha`](…)commit link +Thanks @…` attribution.
  • Forcing node-fetch to throw ERR_STREAM_PREMATURE_CLOSE: no throw, well-formed offline line with commit link + multi-line summary.

🤖 Generated with Claude Code

…h size
The Release workflow's `changeset version` step has failed on the last ~10
runs with `FetchError: ... api.github.com/graphql: Premature close`.
`@changesets/changelog-github` resolves each entry via
`@changesets/get-github-info`, whose DataLoader is created with no
`maxBatchSize`. `apply-release-plan` calls `getReleaseLine` for every
changeset in one `Promise.all`, so all commits fold into a single GraphQL
query. With ~300 changesets accumulated in beta pre mode, that query grows
past what GitHub's GraphQL endpoint reliably serves — it returns a
truncated/gzip-broken body (which node-fetch@2 surfaces as
ERR_STREAM_PREMATURE_CLOSE) or a 502, aborting the whole release.
The custom `.changeset/changelog.mjs` wrapper already aimed to never let a
network call break the release, but only guarded its own backport lookups,
not the upstream delegation that runs node-fetch.
Fix:
- Cap concurrent upstream calls (25) so the DataLoader batches split across
several small queries GitHub can serve, preserving full PR/author
attribution. Verified against the live API: 120 commits -> 5 requests of
<=25 instead of one batch of 120.
- Wrap the upstream delegation in try/catch with a network-free fallback
line, so a transient failure on one batch degrades one entry instead of
failing the release.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@VaguelySerious
VaguelySerious requested review from a team and ijjk as code ownersJune 26, 2026 21:09
@changeset-bot

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fcd41ca

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

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

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

vercelBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production144202301672
✅ 💻 Local Development160502191824
✅ 📦 Local Production160502191824
✅ 🐘 Local Postgres159302311824
✅ 🪟 Windows15200152
✅ 📋 Other88501791064
Total7282010788360

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro125027
✅ example125027
✅ express125027
✅ fastify125027
✅ hono125027
✅ nextjs-turbopack14903
✅ nextjs-webpack14903
✅ nitro125027
✅ nuxt125027
✅ sveltekit14408
✅ vite125027
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable127025
✅ express-stable127025
✅ fastify-stable127025
✅ hono-stable127025
✅ nextjs-turbopack-canary133019
✅ nextjs-turbopack-stable15200
✅ nextjs-webpack-canary133019
✅ nextjs-webpack-stable15200
✅ nitro-stable127025
✅ nuxt-stable127025
✅ sveltekit-stable14606
✅ vite-stable127025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable126026
✅ express-stable126026
✅ fastify-stable126026
✅ hono-stable126026
✅ nextjs-turbopack-canary132020
✅ nextjs-turbopack-stable15101
✅ nextjs-webpack-canary132020
✅ nextjs-webpack-stable15101
✅ nitro-stable126026
✅ nuxt-stable126026
✅ sveltekit-stable14507
✅ vite-stable126026
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack15200
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable127025
✅ e2e-local-dev-tanstack-start-127025
✅ e2e-local-postgres-nest-stable126026
✅ e2e-local-postgres-tanstack-start-126026
✅ e2e-local-prod-nest-stable127025
✅ e2e-local-prod-tanstack-start-127025
✅ e2e-vercel-prod-tanstack-start125027

📋 View full workflow run

@github-actions

github-actionsBot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express0.048s (+3.7%)1.006s (~)0.958s101.00x
💻 LocalNitro0.048s (+8.5% 🔺)1.006s (~)0.958s101.00x
💻 LocalNext.js (Turbopack)0.049s (~)1.006s (~)0.956s101.03x
🐘 PostgresNext.js (Turbopack)0.055s (-9.1% 🟢)1.011s (~)0.957s101.14x
🐘 PostgresExpress0.065s (-13.6% 🟢)1.012s (~)0.947s101.35x
🐘 PostgresNitro0.072s (~)1.013s (~)0.941s101.49x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express0.204s (-27.8% 🟢)1.652s (-13.6% 🟢)1.448s101.00x
▲ VercelNext.js (Turbopack)0.548s (-10.5% 🟢)2.528s (~)1.980s102.69x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro1.082s (~)2.006s (~)0.924s101.00x
💻 LocalExpress1.083s (+0.7%)2.007s (~)0.924s101.00x
🐘 PostgresNext.js (Turbopack)1.085s (-0.9%)2.010s (~)0.926s101.00x
💻 LocalNext.js (Turbopack)1.085s (+0.5%)2.006s (~)0.921s101.00x
🐘 PostgresExpress1.095s (~)2.010s (~)0.915s101.01x
🐘 PostgresNitro1.102s (+0.5%)2.010s (~)0.908s101.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.393s (+2.1%)3.035s (+0.9%)1.641s101.00x
▲ VercelNext.js (Turbopack)2.053s (-12.7% 🟢)3.736s (-0.7%)1.682s101.47x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Nitro10.446s (~)11.022s (~)0.576s31.00x
💻 LocalExpress10.459s (~)11.024s (~)0.565s31.00x
🐘 PostgresNext.js (Turbopack)10.465s (-0.6%)11.015s (~)0.549s31.00x
💻 LocalNext.js (Turbopack)10.469s (~)11.023s (~)0.554s31.00x
🐘 PostgresNitro10.492s (~)11.018s (~)0.526s31.00x
🐘 PostgresExpress10.502s (~)11.020s (~)0.518s31.01x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express12.061s (+2.7%)14.083s (+4.4%)2.022s31.00x
▲ VercelNext.js (Turbopack)13.273s (+4.5%)15.530s (+5.7% 🔺)2.258s21.10x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)13.578s (~)14.027s (~)0.449s51.00x
💻 LocalNitro13.613s (~)14.027s (~)0.414s51.00x
💻 LocalExpress13.631s (+0.5%)14.027s (~)0.397s51.00x
🐘 PostgresNitro13.683s (~)14.020s (~)0.337s51.01x
🐘 PostgresExpress13.766s (+0.6%)14.016s (~)0.250s51.01x
🐘 PostgresNext.js (Turbopack)13.783s (~)14.219s (+1.4%)0.436s51.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express18.000s (+9.3% 🔺)20.026s (+9.4% 🔺)2.026s41.00x
▲ VercelNext.js (Turbopack)18.839s (+6.9% 🔺)20.609s (+6.5% 🔺)1.770s31.05x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express12.073s (-3.0%)12.875s (-1.2%)0.802s71.00x
💻 LocalNitro12.126s (+0.9%)13.026s (+3.0%)0.901s71.00x
💻 LocalNext.js (Turbopack)12.134s (-3.1%)13.025s (-1.1%)0.892s71.01x
💻 LocalExpress12.203s (+1.6%)13.027s (+6.1% 🔺)0.824s71.01x
🐘 PostgresNext.js (Turbopack)12.229s (-2.2%)13.017s (~)0.788s71.01x
🐘 PostgresNitro12.302s (-0.9%)13.022s (~)0.720s71.02x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express19.662s (+7.8% 🔺)21.742s (+8.4% 🔺)2.080s51.00x
▲ VercelNext.js (Turbopack)21.512s (+1.3%)23.389s (+1.6%)1.877s41.09x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.166s (-0.8%)2.008s (~)0.842s151.00x
🐘 PostgresNitro1.182s (-0.6%)2.008s (~)0.825s151.01x
🐘 PostgresNext.js (Turbopack)1.191s (-1.6%)2.007s (-3.3%)0.816s151.02x
💻 LocalNitro1.400s (+1.8%)2.006s (~)0.606s151.20x
💻 LocalExpress1.421s (+1.9%)2.007s (~)0.586s151.22x
💻 LocalNext.js (Turbopack)1.472s (+4.7%)2.007s (~)0.535s151.26x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.561s (+18.3% 🔺)4.068s (+5.3% 🔺)1.508s81.00x
▲ VercelNext.js (Turbopack)4.041s (+25.4% 🔺)5.752s (+17.5% 🔺)1.711s61.58x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.300s (-2.4%)3.008s (~)1.708s101.00x
🐘 PostgresExpress1.331s (-1.3%)2.317s (-7.7% 🟢)0.985s131.02x
🐘 PostgresNitro1.354s (+2.3%)2.394s (-12.5% 🟢)1.040s131.04x
💻 LocalNitro2.447s (-0.7%)2.918s (-3.0%)0.470s111.88x
💻 LocalExpress2.781s (+12.0% 🔺)3.109s (+3.3%)0.328s102.14x
💻 LocalNext.js (Turbopack)2.807s (+16.9% 🔺)3.109s (+3.3%)0.302s102.16x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.787s (+10.9% 🔺)4.403s (+8.4% 🔺)1.616s71.00x
▲ VercelNext.js (Turbopack)3.872s (+8.7% 🔺)6.078s (+16.8% 🔺)2.207s51.39x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.612s (~)4.012s (-5.9% 🟢)2.399s81.00x
🐘 PostgresNitro1.670s (~)4.441s (-6.1% 🟢)2.770s71.04x
🐘 PostgresNext.js (Turbopack)2.288s (-29.4% 🟢)5.182s (-13.9% 🟢)2.894s61.42x
💻 LocalNitro6.144s (-1.0%)6.816s (-2.8%)0.672s53.81x
💻 LocalExpress6.790s (+13.7% 🔺)7.416s (+15.6% 🔺)0.626s54.21x
💻 LocalNext.js (Turbopack)6.878s (+15.4% 🔺)8.268s (+14.6% 🔺)1.391s44.27x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express4.610s (+67.5% 🔺)6.681s (+53.2% 🔺)2.070s51.00x
▲ VercelNext.js (Turbopack)5.407s (+22.7% 🔺)7.280s (+16.6% 🔺)1.873s51.17x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.177s (~)2.008s (~)0.831s151.00x
🐘 PostgresNitro1.189s (+0.6%)2.008s (~)0.819s151.01x
🐘 PostgresNext.js (Turbopack)1.195s (~)2.006s (~)0.812s151.02x
💻 LocalNitro1.415s (+1.2%)2.007s (~)0.592s151.20x
💻 LocalExpress1.416s (+1.4%)2.007s (~)0.590s151.20x
💻 LocalNext.js (Turbopack)1.443s (~)2.006s (~)0.563s151.23x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.246s (+8.3% 🔺)3.921s (+6.6% 🔺)1.674s81.00x
▲ VercelNext.js (Turbopack)3.820s (+19.2% 🔺)5.341s (+10.0% 🔺)1.521s61.70x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.291s (-3.8%)2.316s (+0.9%)1.025s131.00x
🐘 PostgresNext.js (Turbopack)1.294s (-1.6%)3.010s (~)1.716s101.00x
🐘 PostgresNitro1.347s (+4.2%)2.317s (~)0.970s131.04x
💻 LocalNitro2.539s (+5.3% 🔺)3.009s (~)0.470s101.97x
💻 LocalExpress2.694s (+11.9% 🔺)3.009s (~)0.315s102.09x
💻 LocalNext.js (Turbopack)2.710s (+2.7%)3.010s (-3.2%)0.300s102.10x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.881s (+16.6% 🔺)4.624s (+20.8% 🔺)1.743s71.00x
▲ VercelNext.js (Turbopack)4.760s (+39.8% 🔺)6.645s (+38.4% 🔺)1.886s51.65x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.637s (+0.5%)4.447s (+10.9% 🔺)2.811s71.00x
🐘 PostgresNitro1.879s (+14.0% 🔺)4.870s (+17.6% 🔺)2.991s71.15x
🐘 PostgresNext.js (Turbopack)3.326s (+4.1%)6.215s (~)2.889s52.03x
💻 LocalNext.js (Turbopack)6.042s (+7.0% 🔺)6.618s (+6.5% 🔺)0.575s53.69x
💻 LocalExpress6.361s (+14.7% 🔺)7.017s (+12.9% 🔺)0.655s53.89x
💻 LocalNitro7.257s (+22.0% 🔺)8.020s (+24.9% 🔺)0.763s44.43x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.768s (+38.5% 🔺)5.671s (+29.3% 🔺)1.903s61.00x
▲ VercelNext.js (Turbopack)4.935s (-8.6% 🟢)6.674s (-6.1% 🟢)1.738s51.31x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.511s (-17.2% 🟢)1.006s (-1.9%)0.495s601.00x
🐘 PostgresExpress0.551s (-5.4% 🟢)1.023s (-1.8%)0.472s591.08x
🐘 PostgresNitro0.584s (+1.0%)1.006s (-1.6%)0.422s601.14x
💻 LocalNitro0.585s (~)1.022s (~)0.437s591.15x
💻 LocalExpress0.590s (+5.8% 🔺)1.005s (~)0.416s601.15x
💻 LocalNext.js (Turbopack)0.611s (+2.0%)1.022s (+1.7%)0.411s591.20x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.182s (+29.4% 🔺)5.219s (+35.4% 🔺)2.037s121.00x
▲ VercelNext.js (Turbopack)4.964s (+32.2% 🔺)6.751s (+30.3% 🔺)1.786s91.56x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)1.229s (-11.6% 🟢)2.007s (~)0.778s451.00x
🐘 PostgresExpress1.345s (-3.1%)2.053s (~)0.708s441.09x
🐘 PostgresNitro1.412s (+7.7% 🔺)2.054s (+2.3%)0.642s441.15x
💻 LocalNitro1.478s (+3.6%)2.006s (~)0.528s451.20x
💻 LocalExpress1.492s (+3.0%)2.006s (-1.1%)0.514s451.21x
💻 LocalNext.js (Turbopack)1.498s (~)2.006s (~)0.508s451.22x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express8.097s (+38.5% 🔺)10.089s (+35.8% 🔺)1.992s91.00x
▲ VercelNext.js (Turbopack)9.498s (+12.5% 🔺)11.514s (+13.2% 🔺)2.017s81.17x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)2.413s (-13.6% 🟢)3.008s (-3.4%)0.594s401.00x
🐘 PostgresExpress2.517s (-7.9% 🟢)3.034s (-1.7%)0.517s401.04x
🐘 PostgresNitro2.726s (+0.7%)3.086s (-1.6%)0.360s391.13x
💻 LocalNitro3.138s (-2.1%)3.913s (+2.4%)0.775s311.30x
💻 LocalNext.js (Turbopack)3.225s (-1.7%)3.978s (-0.8%)0.753s311.34x
💻 LocalExpress3.300s (+4.4%)4.010s (+5.8% 🔺)0.710s301.37x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express18.146s (+52.1% 🔺)20.493s (+46.9% 🔺)2.347s71.00x
▲ VercelNext.js (Turbopack)22.977s (+21.7% 🔺)24.987s (+19.6% 🔺)2.011s51.27x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.191s (+1.6%)1.023s (+1.5%)0.832s591.00x
🐘 PostgresExpress0.201s (-9.8% 🟢)1.006s (~)0.805s601.05x
🐘 PostgresNitro0.214s (-1.8%)1.006s (~)0.792s601.12x
💻 LocalNitro0.462s (+7.7% 🔺)1.005s (~)0.543s602.41x
💻 LocalExpress0.483s (+13.1% 🔺)1.005s (~)0.522s602.52x
💻 LocalNext.js (Turbopack)0.628s (+6.0% 🔺)1.022s (+1.7%)0.394s593.28x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.370s (+19.6% 🔺)2.945s (+15.5% 🔺)1.575s211.00x
▲ VercelNext.js (Turbopack)2.815s (+36.1% 🔺)4.511s (+21.4% 🔺)1.696s142.05x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.270s (-9.4% 🟢)1.006s (-1.2%)0.736s901.00x
🐘 PostgresExpress0.318s (-5.9% 🟢)1.006s (-1.1%)0.688s901.18x
🐘 PostgresNitro0.345s (+1.2%)1.007s (~)0.662s901.28x
💻 LocalNitro2.079s (-4.1%)2.686s (-4.0%)0.607s347.71x
💻 LocalExpress2.164s (+2.0%)2.767s (+1.1%)0.603s338.02x
💻 LocalNext.js (Turbopack)2.792s (+7.0% 🔺)3.258s (+4.6%)0.466s2810.35x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.990s (+36.3% 🔺)3.997s (+30.3% 🔺)2.006s231.00x
▲ VercelNext.js (Turbopack)3.585s (+23.3% 🔺)5.408s (+12.6% 🔺)1.823s171.80x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Next.js (Turbopack)0.478s (-9.6% 🟢)3.010s (-1.7%)2.532s401.00x
🐘 PostgresExpress0.532s (-2.6%)1.149s (+3.7%)0.618s1051.11x
🐘 PostgresNitro0.545s (-3.0%)1.050s (-3.5%)0.505s1151.14x
💻 LocalNitro10.004s (+6.8% 🔺)11.031s (+5.6% 🔺)1.027s1120.93x
💻 LocalNext.js (Turbopack)10.057s (+6.1% 🔺)11.211s (+6.5% 🔺)1.155s1121.04x
💻 LocalExpress10.417s (+13.4% 🔺)11.305s (+9.1% 🔺)0.887s1121.79x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express2.916s (+52.4% 🔺)4.704s (+22.2% 🔺)1.788s261.00x
▲ VercelNext.js (Turbopack)5.493s (+46.5% 🔺)7.471s (+35.5% 🔺)1.979s171.88x
▲ VercelNitro⚠️missing----

🔍 Observability: Express | Next.js (Turbopack)

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Next.js (Turbopack)1.137s (-1.3%)1.969s (~)0.012s (+17.0% 🔺)2.020s (~)0.883s101.00x
🐘 PostgresExpress1.145s (-1.1%)2.000s (~)0.001s (-21.4% 🟢)2.010s (~)0.865s101.01x
🐘 PostgresNext.js (Turbopack)1.153s (-1.0%)2.000s (~)0.001s (+30.0% 🔺)2.010s (~)0.857s101.01x
💻 LocalNitro1.160s (+0.8%)2.004s (~)0.012s (+9.7% 🔺)2.020s (~)0.859s101.02x
💻 LocalExpress1.164s (+1.1%)2.005s (~)0.012s (+22.8% 🔺)2.021s (~)0.856s101.02x
🐘 PostgresNitro1.166s (-2.1%)1.999s (~)0.001s (-26.7% 🟢)2.011s (~)0.845s101.03x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express1.974s (-7.9% 🟢)3.569s (+9.0% 🔺)2.038s (+21.1% 🔺)6.129s (+12.0% 🔺)4.155s101.00x
▲ VercelNext.js (Turbopack)3.986s (+28.8% 🔺)4.016s (+6.1% 🔺)1.953s (+42.3% 🔺)7.748s (+23.1% 🔺)3.761s102.02x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.540s (-0.8%)2.003s (~)0.005s (+4.7%)2.026s (~)0.486s301.00x
💻 LocalNitro1.547s (+0.6%)2.009s (~)0.013s (+3.7%)2.025s (~)0.479s301.00x
🐘 PostgresNext.js (Turbopack)1.558s (-3.7%)2.009s (~)0.005s (+2.6%)2.026s (~)0.467s301.01x
💻 LocalExpress1.574s (+2.7%)2.011s (~)0.013s (+0.8%)2.026s (~)0.453s301.02x
💻 LocalNext.js (Turbopack)1.584s (~)1.971s (~)0.011s (-23.6% 🟢)2.024s (~)0.441s301.03x
🐘 PostgresNitro1.597s (+0.7%)2.002s (~)0.005s (+3.4%)2.026s (~)0.428s301.04x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express6.367s (+16.0% 🔺)7.885s (+13.5% 🔺)0.250s (+8.7% 🔺)8.629s (+11.5% 🔺)2.261s71.00x
▲ VercelNext.js (Turbopack)10.932s (+18.7% 🔺)11.820s (+8.5% 🔺)0.243s (-3.3%)13.252s (+14.1% 🔺)2.320s51.72x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express0.769s (-6.4% 🟢)1.065s (~)0.000s (-32.1% 🟢)1.102s (+2.2%)0.333s551.00x
🐘 PostgresNitro0.806s (-1.8%)1.045s (-5.2% 🟢)0.000s (-36.8% 🟢)1.060s (-5.4% 🟢)0.253s571.05x
🐘 PostgresNext.js (Turbopack)1.006s (+0.8%)1.418s (-2.9%)0.000s (-100.0% 🟢)1.429s (-3.7%)0.422s431.31x
💻 LocalExpress1.240s (+0.6%)2.013s (~)0.000s (-60.0% 🟢)2.015s (~)0.776s301.61x
💻 LocalNitro1.268s (~)2.013s (~)0.000s (+14.3% 🔺)2.016s (~)0.748s301.65x
💻 LocalNext.js (Turbopack)1.354s (-2.9%)1.979s (~)0.001s (+77.8% 🔺)2.016s (~)0.662s301.76x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express3.701s (+27.1% 🔺)5.023s (+15.1% 🔺)0.002s (+Infinity% 🔺)5.526s (+14.0% 🔺)1.825s111.00x
▲ VercelNext.js (Turbopack)5.168s (+6.3% 🔺)6.181s (+10.6% 🔺)0.000s (NaN%)6.952s (+1.2%)1.784s91.40x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

fan-out fan-in 10 streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.703s (+0.7%)2.301s (~)0.000s (+Infinity% 🔺)2.316s (~)0.613s261.00x
🐘 PostgresNitro1.770s (-3.3%)2.301s (-3.2%)0.000s (-100.0% 🟢)2.332s (-3.0%)0.562s261.04x
🐘 PostgresNext.js (Turbopack)2.551s (-17.5% 🟢)3.051s (-13.6% 🟢)0.000s (-71.7% 🟢)3.059s (-13.6% 🟢)0.508s201.50x
💻 LocalNitro3.469s (+5.1% 🔺)4.095s (+1.7%)0.000s (-30.0% 🟢)4.099s (+1.7%)0.630s152.04x
💻 LocalExpress3.538s (+7.8% 🔺)4.027s (+4.9%)0.001s (+113.3% 🔺)4.030s (+4.9%)0.492s152.08x
💻 LocalNext.js (Turbopack)3.541s (~)4.058s (+1.7%)0.001s (+113.3% 🔺)4.098s (+1.4%)0.557s152.08x

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Express5.371s (+2.8%)6.997s (+7.5% 🔺)0.000s (-100.0% 🟢)7.482s (+7.0% 🔺)2.111s91.00x
▲ VercelNext.js (Turbopack)8.546s (+24.9% 🔺)8.850s (+15.0% 🔺)0.000s (-100.0% 🟢)10.343s (+19.1% 🔺)1.797s61.59x
▲ VercelNitro⚠️missing-----

🔍 Observability: Express | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalNitro16/21
🐘 PostgresNext.js (Turbopack)10/21
▲ VercelExpress21/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework🥇 Fastest WorldWins
Express🐘 Postgres17/21
Next.js (Turbopack)🐘 Postgres17/21
Nitro🐘 Postgres14/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)
  • 🌐 Platformatic: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

Comment thread.changeset/changelog.mjs Outdated
…tten summary verbatim, leaking injected `pr:`/`commit:` directive lines into the changelog so the entry title becomes literally `pr: #N`.
This commit fixes the issue reported at .changeset/changelog.mjs:368
## Bug
`maybeRewriteChangesetForBackport` prepends directive lines to a backport changeset's summary:
```js
const rewrittenSummary =
`pr: #${originalPRNumber}\n` +
`commit: ${changeset.commit}\n` +
summary;
return { ...changeset, summary: rewrittenSummary };
```
The upstream `@changesets/changelog-github` `getReleaseLine` consumes those directives and **strips them** from the summary (via `.replace(/^\s*(?:pr|pull|pull\s+request):.../im, ...)`, `commit:`, `author:` etc.) before splitting into `firstLine`/`futureLines` and rendering. So in the normal path the directives never appear in the output — that's the whole point of injecting them.
The offline fallback, however, does **not** strip them:
```js
function offlineReleaseLine(changeset, options) {
const [firstLine, ...futureLines] = (changeset.summary ?? '')
.split('\n')
.map((l) => l.trimEnd());
...
}
```
It just splits on `\n` and renders verbatim.
### Concrete trigger
A backport changeset where:
1. `maybeRewriteChangesetForBackport` **succeeds** (network up, PR resolved, directives injected → `rewritten.summary` starts with `pr: #N\ncommit: <sha>\n`), and
2. the subsequent `limitUpstream(() => upstream.getReleaseLine(rewritten, ...))` **throws** — exactly the flaky/oversized GraphQL batch scenario this fallback was added for.
The catch block then ran `offlineReleaseLine(rewritten, options)`, producing:
```
- [`abc1234`](...) - pr: #123
commit: abc1234...
<real first line>
```
The entry title becomes literally `pr: #123` and `commit: …` leaks as body text.
## Fix
Pass the **original** `changeset` to `offlineReleaseLine` instead of `rewritten`. The backport rewrite only mutates `summary`; `changeset.commit` is untouched, so the offline commit link is identical, while the original summary has no injected directives. The offline path already drops PR/author attribution it can't resolve, so using the un-rewritten summary is exactly correct.
The dependency-line path (`offlineDependencyReleaseLine`) is unaffected since no directive injection happens there.
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: VaguelySerious <mittgfu@gmail.com>

@pranaygppranaygp 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.

Reviewed the diff and verified the core mechanism end to end against the real file (stubbed upstream, Node 22.18, require()-able ESM confirmed):

  • Limiter works as claimed: 120 getReleaseLine calls → max 25 concurrent → ~5 DataLoader batches of ≤25, order preserved. Since get-github-info's DataLoader is a module-level singleton built with no maxBatchSize (confirmed in get-github-info@0.7.0 src) and each getReleaseLine fires exactly one .load(), gating concurrency at 25 genuinely bounds the per-batch query size while preserving full PR/author attribution. Good, well-reasoned fix.
  • Offline getReleaseLine mirrors the upstream template exactly (changelog-github@0.5.2): `\n\n-${prefix ? ` ${prefix} -` : ''} ${firstLine}...`, dropping only the pull/Thanks attribution. The 2nd-commit fix (feed the original changeset, not rewritten) is correct and I confirmed it no longer leaks pr:/commit: directives.
  • Offline dependency line is lossless — dep lines only ever render commit links, which you reconstruct from the SHA, so that path loses nothing even when it falls back.

LGTM to ship — it unblocks the release. A few non-blocking notes inline (one substantive: the dependency-rollup path isn't actually batch-bounded by the limiter and is carried entirely by the fallback).

// truncated/gzip-broken body (which `node-fetch@2` surfaces as
// `ERR_STREAM_PREMATURE_CLOSE`) or a 502, aborting the whole release.
// 6. Wraps the upstream delegation in a try/catch with a network-free
// fallback line, so a transient failure on one batch degrades a single

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.

Minor wording: "degrades a single entry" undersells it. When an oversized getReleaseLine batch fails, every .load() sharing that one fetch rejects together, so a whole batch (~UPSTREAM_CONCURRENCY, up to 25 entries) drops to the offline line at once — not one. The important half ("instead of failing the release") still holds; just the count is off.

options,
);
try {
return await limitUpstream(() =>

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.

The concurrency gate bounds the getReleaseLine batch nicely, but it does not bound this path's batch. apply-release-plan's getChangelogEntry calls getDependencyReleaseLine once per release with relevantChangesets (every changeset that bumped that package's deps), and upstream fans out to one getInfo() per changeset inside a single internal Promise.all — so all of those .load()s land in one DataLoader batch within this one limiter slot. A package whose deps were bumped by hundreds of changesets (e.g. the many packages depending on @workflow/core) therefore still produces one oversized query here, exactly the shape this PR is fixing.

In practice the release stays safe: that query throws → catchofflineDependencyReleaseLine, which is lossless for dependency lines (they only render commit links). So no functional gap. But point 5's rationale — "splits its lookups across several small GraphQL queries" — only really applies to getReleaseLine; this path is carried by the fallback, not by batch-splitting. Worth a word in the comments so the next reader doesn't assume both paths are bounded (or, if you want it truly bounded, gate the per-getInfo fan-out rather than the outer call).

// the changelog (the entry title would become literally `pr: #N`). The
// rewrite only touches `summary`; `changeset.commit` is unchanged, so the
// offline commit link is identical either way.
return offlineReleaseLine(changeset, options);

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.

Good fix on the backport-rewrite leak. One residual of the same class: if a changeset's summary manually contains a pr:/commit:/author: directive, maybeRewriteChangesetForBackport returns it unchanged (the guard at L309), upstream would strip it before rendering — but the offline path renders it verbatim, so the entry title becomes literally pr: #123 (confirmed locally). Much rarer than the backport case (needs a hand-written directive and an upstream failure), so not a blocker. If you want the offline path to fully match upstream, strip the leading directive lines here the same way upstream's .replace(/^\s*(?:pr|pull|...|author|user):/im, '') chain does.

: ` \`${short}\``;
}

function offlineReleaseLine(changeset, options) {

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.

Optional: these offline helpers + the limiter are pure and easy to unit-test, and the directive leak already regressed once (needed a follow-up commit), which is a good signal this file is edit-fragile. A tiny test — force upstream to throw and assert the offline line keeps the commit link + a clean title, plus assert 120 calls never exceed 25 concurrent — would be cheap regression insurance. No test runner is wired for .changeset/ today, so low priority, but worth considering.

@VaguelySerious
VaguelySerious merged commit 7f04d87 into mainJun 26, 2026
112 of 113 checks passed
@VaguelySerious
VaguelySerious deleted the peter/fix-release-changelog-premature-close branch June 26, 2026 21:42
github-actionsBot added a commit that referenced this pull request Jun 26, 2026
…h size (#2669)
Signed-off-by: Peter Wielander <mittgfu@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #2671. (backport job run)

VaguelySerious added a commit that referenced this pull request Jun 26, 2026
…h size (#2669) (#2671)
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
pranaygp added a commit that referenced this pull request Jun 26, 2026
…edrive
* origin/main: (90 commits)
[ai] Fix "missing text part" stream crash by repairing UI message framing (#2537)
[ci] Fix release "Premature close" by bounding changelog GraphQL batch size (#2669)
[ai] Drop orphan UI chunks after negative-index reconnect (#2082)
Fix trace detail panel Input/Output flicker (selection-driven state machine) (#2637)
[world-local] Scope untagged startup recovery to untagged runs (#2667)
fix(world-postgres): defer loopback worker startup (#2657)
docs: deprecate DurableAgent and WorkflowChatTransport, point to AI SDK WorkflowAgent (#2616)
[web] Fix HTTP/2 in bundled server build so observability reads work (#2632)
[core] Fix turbo-mode step-body writes racing run_started (#2629)
fix(world-postgres): rename setup command (#2644)
chore: ignore workflow swc caches (#2640)
fix(ai): ignore intentional stream aborts (#2635)
fix(nitro): reload steps during Vite HMR (#2572)
[world] Make @workflow/world own its Zod dependency (#2622)
feat(web-shared): extend cn with custom tailwind-merge class groups (#2619)
[world-vercel] Enable HTTP/2 for the events API and stream writes (#2573)
Remove shadow from off-screen marker indicator button (#2614)
Fix discovery of dotted JS workflow imports (#2594)
Version Packages (beta) (#2597)
Thread occurredAt into web-shared entities (#2608)
...
# Conflicts:
#	packages/core/src/runtime/step-executor.ts
#	packages/world-vercel/src/http-client.ts
Sign up for freeto 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.

2 participants

@VaguelySerious@pranaygp