Add dev-tmux skill for portless+tmux local Workflow SDK dev - #1916

Merged
pranaygp merged 11 commits into
mainfrom
pgp/stepflow-test
May 5, 2026
Merged

Add dev-tmux skill for portless+tmux local Workflow SDK dev#1916
pranaygp merged 11 commits into
mainfrom
pgp/stepflow-test

Conversation

@pranaygp

@pranaygppranaygp commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • New dev-tmux skill documenting the 3-pane tmux + portless setup for local Workflow SDK development (turbopack workbench, observability UI scoped to that workbench, scratchpad shell — all worktree-isolated via portless's branch-prefixed .localhost URLs).
  • Adds allowedDevOrigins: ['turbopack.localhost', '*.turbopack.localhost'] to workbench/nextjs-turbopack/next.config.ts so portless-style URLs don't get blocked by Next's cross-origin protection in dev (currently floods the logs with HMR warnings).

What changed since the original PR

The original PR also added two race workflows (sleepWinsRaceWorkflow, stepWinsRaceWorkflow) and their e2e tests to surface a Promise.race-vs-replay semantics issue. Those landed independently in #1924 (with the underlying fix), so this branch was rebased onto main and the duplicate additions removed — the race tests now come from main. This PR is dev-tooling only.

Test plan

  • pnpm vitest run packages/core/e2e/e2e.test.ts -t WinsRaceWorkflow against the turbopack workbench (passes after rebase, since [core] V2: skip inline step execution when suspension also has a wait #1924's fix is on main)
  • Spin up the workbench by following skills/dev-tmux/SKILL.md and confirm both https://<branch>.turbopack.localhost (HMR works, no cross-origin warnings) and https://<branch>.workflow-obs.localhost (observability UI shows runs from the workbench)

🤖 Generated with Claude Code

Adds two race workflows (sleepWinsRaceWorkflow, stepWinsRaceWorkflow)
that exercise Promise.race between a step function and a sleep call.
The current `sleepWinsRaceWorkflow` test fails — surfacing how the
replay engine resolves a previously-completed step instantly while
sleep still has to elapse.
Also adds a `dev-tmux` skill that documents the 3-pane tmux + portless
setup for testing workflows interactively in a worktree alongside the
observability UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings May 4, 2026 11:48
@changeset-bot

changeset-botBot commented May 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8b7225b

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 May 4, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 4, 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🥇 Nitro0.031s (-27.8% 🟢)1.005s (~)0.974s101.00x
💻 LocalExpress0.034s (-22.3% 🟢)1.005s (~)0.970s101.11x
🐘 PostgresExpress0.046s (-20.7% 🟢)1.012s (~)0.966s101.48x
🐘 PostgresNitro0.046s (-51.7% 🟢)1.012s (-3.0%)0.966s101.48x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.315s (-23.1% 🟢)2.072s (-17.4% 🟢)1.758s101.00x
▲ VercelNext.js (Turbopack)0.793s (+215.3% 🔺)2.620s (+12.3% 🔺)1.827s102.52x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.068s (-5.1% 🟢)2.006s (~)0.938s101.00x
💻 LocalNitro1.073s (-5.2% 🟢)2.006s (~)0.933s101.00x
🐘 PostgresNitro1.083s (-5.0%)2.011s (~)0.927s101.01x
🐘 PostgresExpress1.085s (-5.4% 🟢)2.010s (~)0.925s101.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.777s (-54.3% 🟢)3.967s (-32.9% 🟢)2.190s101.00x
▲ VercelNext.js (Turbopack)2.267s (+11.4% 🔺)3.754s (-2.0%)1.487s101.28x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.402s (-4.8%)11.020s (~)0.618s31.00x
🐘 PostgresNitro10.402s (-4.3%)11.017s (~)0.615s31.00x
💻 LocalNitro10.414s (-4.9%)11.022s (~)0.607s31.00x
🐘 PostgresExpress10.446s (-4.7%)11.019s (~)0.574s31.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.463s (-43.3% 🟢)15.032s (-40.2% 🟢)1.568s21.00x
▲ VercelNext.js (Turbopack)14.098s (-18.6% 🟢)15.843s (-18.3% 🟢)1.745s21.05x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.436s (-10.2% 🟢)14.025s (-6.7% 🟢)0.589s51.00x
🐘 PostgresNitro13.445s (-7.9% 🟢)14.018s (-6.7% 🟢)0.573s51.00x
💻 LocalNitro13.481s (-10.5% 🟢)14.027s (-12.5% 🟢)0.546s51.00x
🐘 PostgresExpress13.493s (-7.5% 🟢)14.024s (-6.6% 🟢)0.531s51.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro21.085s (-67.3% 🟢)23.044s (-65.4% 🟢)1.959s31.00x
▲ VercelNext.js (Turbopack)22.401s (-57.4% 🟢)24.203s (-55.7% 🟢)1.802s31.06x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro11.880s (-14.9% 🟢)12.016s (-16.0% 🟢)0.136s81.00x
💻 LocalNitro11.939s (-28.9% 🟢)12.147s (-28.7% 🟢)0.208s81.00x
💻 LocalExpress11.974s (-27.9% 🟢)12.396s (-27.2% 🟢)0.422s81.01x
🐘 PostgresExpress12.130s (-13.4% 🟢)12.642s (-13.4% 🟢)0.512s81.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro30.301s (-92.8% 🟢)32.393s (-92.4% 🟢)2.092s31.00x
▲ VercelNext.js (Turbopack)34.399s (-91.3% 🟢)36.050s (-90.9% 🟢)1.651s31.14x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.156s (-22.3% 🟢)2.005s (~)0.849s151.00x
🐘 PostgresNitro1.159s (-9.1% 🟢)2.008s (~)0.849s151.00x
🐘 PostgresExpress1.160s (-8.0% 🟢)2.008s (~)0.848s151.00x
💻 LocalNitro1.180s (-27.6% 🟢)2.006s (-3.3%)0.825s151.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.412s (-14.4% 🟢)3.982s (-7.9% 🟢)1.570s81.00x
▲ VercelNext.js (Turbopack)4.906s (+44.4% 🔺)6.610s (+34.0% 🔺)1.704s52.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.228s (-48.0% 🟢)2.008s (-33.3% 🟢)0.780s151.00x
🐘 PostgresNitro1.238s (-47.3% 🟢)2.006s (-33.3% 🟢)0.768s151.01x
💻 LocalExpress1.598s (-45.9% 🟢)2.005s (-41.9% 🟢)0.407s151.30x
💻 LocalNitro1.894s (-39.8% 🟢)2.149s (-44.7% 🟢)0.256s141.54x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.300s (-18.6% 🟢)5.165s (-12.8% 🟢)1.865s61.00x
▲ VercelNext.js (Turbopack)5.590s (-21.3% 🟢)7.240s (-18.7% 🟢)1.651s51.69x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.380s (-60.4% 🟢)2.009s (-49.9% 🟢)0.629s151.00x
🐘 PostgresNitro1.390s (-60.0% 🟢)2.008s (-49.9% 🟢)0.617s151.01x
💻 LocalExpress4.503s (-46.0% 🟢)4.725s (-47.7% 🟢)0.222s73.26x
💻 LocalNitro5.496s (-34.2% 🟢)6.014s (-33.3% 🟢)0.518s53.98x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)5.990s (-32.8% 🟢)7.487s (-31.7% 🟢)1.496s51.00x
▲ VercelNitro6.899s (+95.7% 🔺)10.766s (+94.6% 🔺)3.867s31.15x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.159s (-7.8% 🟢)2.009s (~)0.850s151.00x
🐘 PostgresNitro1.165s (-7.4% 🟢)2.009s (~)0.844s151.00x
💻 LocalExpress1.317s (-30.5% 🟢)2.005s (-15.2% 🟢)0.689s151.14x
💻 LocalNitro1.415s (-24.1% 🟢)2.007s (-14.3% 🟢)0.591s151.22x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.462s (~)3.978s (-4.6%)1.516s81.00x
▲ VercelNext.js (Turbopack)5.008s (+70.8% 🔺)6.602s (+42.2% 🔺)1.594s52.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.221s (-47.8% 🟢)2.009s (-33.3% 🟢)0.788s151.00x
🐘 PostgresExpress1.241s (-47.0% 🟢)2.008s (-33.3% 🟢)0.767s151.02x
💻 LocalExpress1.666s (-46.8% 🟢)2.005s (-46.7% 🟢)0.339s151.36x
💻 LocalNitro2.096s (-31.6% 🟢)2.592s (-33.3% 🟢)0.496s121.72x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.255s (+0.7%)5.219s (+2.8%)1.965s61.00x
▲ VercelNext.js (Turbopack)4.456s (+41.8% 🔺)6.286s (+39.0% 🔺)1.830s51.37x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.358s (-61.0% 🟢)2.007s (-49.9% 🟢)0.649s151.00x
🐘 PostgresExpress1.379s (-60.6% 🟢)2.008s (-49.9% 🟢)0.628s151.02x
💻 LocalExpress4.938s (-43.9% 🟢)5.513s (-40.6% 🟢)0.575s63.64x
💻 LocalNitro6.426s (-29.7% 🟢)6.819s (-32.0% 🟢)0.393s54.73x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.698s (-7.8% 🟢)6.786s (~)2.088s51.00x
▲ VercelNext.js (Turbopack)5.481s (-18.9% 🟢)7.171s (-16.1% 🟢)1.689s51.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.429s (-47.8% 🟢)1.006s (~)0.578s601.00x
🐘 PostgresExpress0.435s (-48.2% 🟢)1.007s (-1.6%)0.572s601.01x
💻 LocalExpress0.444s (-54.8% 🟢)1.004s (-6.7% 🟢)0.559s601.04x
💻 LocalNitro0.471s (-52.0% 🟢)1.004s (-8.2% 🟢)0.534s601.10x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.925s (-77.7% 🟢)7.020s (-70.8% 🟢)2.095s91.00x
▲ VercelNext.js (Turbopack)6.772s (-53.3% 🟢)8.207s (-49.0% 🟢)1.435s81.37x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.028s (-46.7% 🟢)1.413s (-32.7% 🟢)0.384s641.00x
🐘 PostgresExpress1.065s (-46.1% 🟢)1.771s (-21.5% 🟢)0.706s511.04x
💻 LocalExpress1.125s (-62.7% 🟢)2.005s (-44.1% 🟢)0.880s451.09x
💻 LocalNitro1.184s (-61.0% 🟢)2.006s (-46.6% 🟢)0.822s451.15x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro12.382s (-68.6% 🟢)14.665s (-64.5% 🟢)2.283s71.00x
▲ VercelNext.js (Turbopack)15.639s (-68.6% 🟢)17.695s (-65.8% 🟢)2.056s61.26x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.974s (-51.9% 🟢)2.380s (-48.3% 🟢)0.405s511.00x
🐘 PostgresExpress2.076s (-48.0% 🟢)2.529s (-42.1% 🟢)0.453s481.05x
💻 LocalExpress2.545s (-72.4% 🟢)3.032s (-69.7% 🟢)0.488s401.29x
💻 LocalNitro2.664s (-71.4% 🟢)3.057s (-69.5% 🟢)0.394s401.35x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro38.936s (-59.8% 🟢)41.471s (-57.9% 🟢)2.535s41.00x
▲ VercelNext.js (Turbopack)45.359s (-57.7% 🟢)47.056s (-56.8% 🟢)1.697s31.16x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.186s (-34.3% 🟢)1.005s (~)0.819s601.00x
🐘 PostgresExpress0.200s (-29.0% 🟢)1.006s (~)0.806s601.08x
💻 LocalExpress0.389s (-30.6% 🟢)1.004s (~)0.615s602.09x
💻 LocalNitro0.441s (-27.1% 🟢)1.004s (-1.7%)0.563s602.37x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.370s (+42.7% 🔺)3.938s (+17.5% 🔺)1.568s161.00x
▲ VercelNext.js (Turbopack)3.739s (+84.9% 🔺)5.211s (+37.4% 🔺)1.473s121.58x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.307s (-38.1% 🟢)1.006s (~)0.699s901.00x
🐘 PostgresExpress0.329s (-35.5% 🟢)1.006s (~)0.678s901.07x
💻 LocalExpress2.111s (-16.0% 🟢)2.684s (-10.8% 🟢)0.573s346.88x
💻 LocalNitro2.201s (-13.3% 🟢)2.945s (-2.1%)0.744s317.17x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.219s (+123.8% 🔺)9.007s (+86.8% 🔺)1.788s101.00x
▲ VercelNext.js (Turbopack)7.230s (+104.5% 🔺)8.888s (+71.1% 🔺)1.657s111.00x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.623s (-21.2% 🟢)1.006s (~)0.383s1201.00x
🐘 PostgresExpress0.676s (-17.4% 🟢)1.007s (-1.1%)0.330s1201.09x
💻 LocalExpress8.906s (-20.4% 🟢)9.485s (-20.6% 🟢)0.579s1314.30x
💻 LocalNitro10.253s (-8.4% 🟢)10.694s (-8.3% 🟢)0.441s1216.47x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)22.466s (+117.5% 🔺)23.969s (+95.1% 🔺)1.504s61.00x
▲ VercelNitro24.505s (+217.3% 🔺)26.705s (+184.1% 🔺)2.200s51.09x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.120s (+462.6% 🔺)2.004s (+99.5% 🔺)0.009s (-22.3% 🟢)2.016s (+98.0% 🔺)0.896s101.00x
💻 LocalNitro1.134s (+430.7% 🔺)2.006s (+99.7% 🔺)0.012s (-0.8%)2.020s (+98.3% 🔺)0.886s101.01x
🐘 PostgresNitro1.140s (+456.2% 🔺)2.002s (+100.3% 🔺)0.001s (-13.3% 🟢)2.011s (+98.8% 🔺)0.871s101.02x
🐘 PostgresExpress1.159s (+465.2% 🔺)1.998s (+100.1% 🔺)0.001s (-12.5% 🟢)2.011s (+98.8% 🔺)0.852s101.03x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.439s (-36.4% 🟢)3.535s (-33.0% 🟢)2.339s (+215.2% 🔺)6.477s (~)4.038s101.00x
▲ VercelNext.js (Turbopack)4.213s (-38.5% 🟢)4.295s (-50.3% 🟢)1.310s (+107.3% 🔺)7.294s (-25.5% 🟢)3.081s101.73x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.488s (+138.4% 🔺)2.003s (+99.0% 🔺)0.004s (-3.3%)2.025s (+98.0% 🔺)0.536s301.00x
💻 LocalNitro1.523s (+81.6% 🔺)2.012s (+98.8% 🔺)0.010s (+5.3% 🔺)2.023s (+81.3% 🔺)0.500s301.02x
🐘 PostgresExpress1.540s (+144.4% 🔺)2.009s (+99.6% 🔺)0.004s (+3.6%)2.026s (+98.0% 🔺)0.486s301.03x
💻 LocalExpress1.665s (+119.9% 🔺)2.009s (+95.2% 🔺)0.009s (-1.0%)2.199s (+111.5% 🔺)0.534s281.12x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.887s (-80.0% 🟢)7.416s (-75.9% 🟢)0.184s (+64.0% 🔺)8.100s (-74.5% 🟢)2.213s81.00x
▲ VercelNext.js (Turbopack)13.623s (-19.5% 🟢)13.512s (-25.9% 🟢)0.199s (-5.7% 🟢)15.368s (-18.8% 🟢)1.744s42.31x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.653s (-32.6% 🟢)1.050s (-15.9% 🟢)0.000s (-57.9% 🟢)1.060s (-15.7% 🟢)0.407s571.00x
🐘 PostgresExpress0.672s (-30.0% 🟢)1.033s (-19.2% 🟢)0.000s (+19.0% 🔺)1.042s (-20.2% 🟢)0.370s581.03x
💻 LocalExpress1.274s (+4.0%)2.013s (~)0.000s (-40.0% 🟢)2.015s (~)0.740s301.95x
💻 LocalNitro1.310s (+7.1% 🔺)2.017s (~)0.001s (+400.0% 🔺)2.019s (~)0.709s302.01x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.313s (+8.6% 🔺)4.592s (+4.5%)0.000s (+8.3% 🔺)5.023s (+4.5%)1.710s121.00x
▲ VercelNext.js (Turbopack)5.512s (-45.9% 🟢)6.062s (-47.4% 🟢)0.000s (+Infinity% 🔺)6.982s (-42.1% 🟢)1.470s91.66x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.168s (-34.8% 🟢)1.996s (-6.8% 🟢)0.000s (-100.0% 🟢)2.026s (-6.8% 🟢)0.858s301.00x
🐘 PostgresExpress1.377s (-22.3% 🟢)2.066s (-5.1% 🟢)0.000s (NaN%)2.082s (-5.3% 🟢)0.705s291.18x
💻 LocalExpress2.968s (-14.4% 🟢)3.555s (-11.9% 🟢)0.000s (-70.6% 🟢)3.559s (-11.8% 🟢)0.591s172.54x
💻 LocalNitro3.084s (-9.0% 🟢)3.781s (-6.2% 🟢)0.000s (-29.7% 🟢)3.783s (-6.3% 🟢)0.700s162.64x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.124s (+25.2% 🔺)6.356s (+18.3% 🔺)0.000s (-100.0% 🟢)6.813s (+17.6% 🔺)1.689s91.00x
▲ VercelNext.js (Turbopack)7.535s (+34.2% 🔺)8.909s (+27.6% 🔺)0.000s (-100.0% 🟢)9.344s (+23.9% 🔺)1.809s71.47x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress18/21
🐘 PostgresNitro17/21
▲ VercelNitro19/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

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

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actionsBot commented May 4, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production92502191144
✅ 💻 Local Development123702191456
✅ 📦 Local Production123702191456
✅ 🐘 Local Postgres123702191456
✅ 🪟 Windows10400104
✅ 📋 Other5520176728
Total5292010526344

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro78026
✅ example78026
✅ express78026
✅ fastify78026
✅ hono78026
✅ nextjs-turbopack10202
✅ nextjs-webpack10202
✅ nitro78026
✅ nuxt78026
✅ sveltekit9707
✅ vite78026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack10400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable79025
✅ e2e-local-dev-tanstack-start-79025
✅ e2e-local-postgres-nest-stable79025
✅ e2e-local-postgres-tanstack-start-79025
✅ e2e-local-prod-nest-stable79025
✅ e2e-local-prod-tanstack-start-79025
✅ e2e-vercel-prod-tanstack-start78026

📋 View full workflow run

Adds allowedDevOrigins entries so portless-style worktree-prefixed
.localhost URLs (e.g. https://<branch>.turbopack.localhost) can hit
HMR and dev-only endpoints without Next's cross-origin protection
flooding the logs with warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

CopilotAI 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.

Pull request overview

Adds new end-to-end coverage for Promise.race behavior between a long-running step and sleep(), and introduces a new dev-tmux skill to standardize a local 3-pane dev setup (workbench + observability UI + scratchpad) using portless for worktree-isolated .localhost URLs.

Changes:

  • Add sleepWinsRaceWorkflow / stepWinsRaceWorkflow workflows to the workbench e2e workflow collection.
  • Add corresponding Vitest e2e tests asserting the expected race winner and duration bounds.
  • Add skills/dev-tmux/SKILL.md documentation for a tmux + portless local dev workflow; add a (currently empty) changeset file.

Reviewed changes

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

FileDescription
workbench/example/workflows/99_e2e.tsAdds two new workflows plus a shared step helper to exercise Promise.race between a step and sleep().
packages/core/e2e/e2e.test.tsAdds two e2e tests covering the new race workflows and asserting winner + duration.
skills/dev-tmux/SKILL.mdNew skill documenting a portless-routed 3-pane tmux setup for local SDK dev + e2e runs.
.changeset/better-pets-reply.mdAdds a new changeset file, but it is currently empty/invalid.

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

Comment on lines +584 to +590
test('sleepWinsRaceWorkflow', { timeout: 60_000 }, async () => {
const run = await start(await e2e('sleepWinsRaceWorkflow'), []);
const returnValue = await run.returnValue;
expect(returnValue.winner).toBe('sleep');
// Sleep is 1s; step would take 10s. Should resolve in ~1s, well under 5s.
expect(returnValue.durationMs).toBeLessThan(5_000);
});

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Obsolete after rebase onto main — PR #1924 fixed the underlying race semantics and added these same tests directly to main. The duplicate set on this branch was dropped (031e4b8); the race tests are no longer part of this PR's diff.

Comment thread.changeset/better-pets-reply.md
Comment threadskills/dev-tmux/SKILL.md Outdated
Comment on lines +51 to +59
tmux send-keys -t "$SESSION".1 \
'cd workbench/nextjs-turbopack && WORKFLOW_PUBLIC_MANIFEST=1 portless run --name turbopack pnpm dev' C-m

# Pane 2 (top-right): observability UI scoped to the workbench app
tmux send-keys -t "$SESSION".2 \
'cd workbench/nextjs-turbopack && portless run --name workflow-obs sh -c "pnpm workflow web --webPort \$PORT --noBrowser"' C-m

# Pane 3 (bottom-right): scratchpad at repo root
tmux send-keys -t "$SESSION".3 'echo "scratchpad: $(pwd)"' C-m

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Fixed in 5a44360. The skill now captures each pane's ID at split time with -P -F '#{pane_id}' and uses those IDs as targets, so it's correct under both pane-base-index 0 and pane-base-index 1 regardless of the user's tmux config.

Comment threadpackages/core/e2e/e2e.test.ts
TooTallNate
TooTallNate previously requested changes May 4, 2026

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verdict: Request Changes

The PR has two things in it: e2e race tests (good idea, surfacing a real V2-handler semantics question) and a personal-dev skill file. I have one blocker on the test, one scope concern on the skill, and one minor nit on the next.config change.


Summary of inline comments

  1. packages/core/e2e/e2e.test.ts:587 — Blocker.sleepWinsRaceWorkflow fails on every e2e job (56+ jobs across all frameworks × all environments). Needs test.fails(...), .skip with TODO, or removal until the underlying race semantics are decided. The PR description correctly identifies this as intentional pending discussion, but the current state makes the e2e matrix unusable for any PR based on this one.

  2. skills/dev-tmux/SKILL.md:3 — Scope. Activation phrases ("spin up the dev session", "start dev mode", "set up the workbench") will trigger for general contributor questions, then push them toward installing portless. Either narrow the phrases to portless-specific terms or move the skill to a user-level location.

  3. workbench/nextjs-turbopack/next.config.ts:13 — Nit. Hardcoding *.turbopack.localhost in the workbench bakes one developer's convention into the shared config. Dev-only, so harmless — flagging for awareness, not blocking.


What I verified

  • Diagnosis is correct: The V2 inline-execution model in workflowEntrypoint (packages/core/src/runtime.ts) executes step bodies inline rather than enqueueing them. A 10-second delayMsStep blocks the handler for 10 seconds, which prevents the 1-second sleep('1s') timer from firing on time. So Promise.race ends up resolving with 'step' because that's what the handler delivers first. Whether this should be considered correct behavior or a bug is a real question.
  • Symlinks: Confirmed 99_e2e.ts is symlinked from workbench/example/workflows/99_e2e.ts into all other workbenches per AGENTS.md convention. ✅
  • Empty changeset: ---\n--- is the right pattern for tests-only PRs that don't ship package changes; matches existing examples (fix-vite-step-source-maps-e2e.md, tanstack-start-workbench.md). ✅
  • Skill frontmatter: version: '1.0' is fine for a new skill (existing migrating-to-workflow-sdk is '0.2.0'). Format matches.
  • Typecheck: Clean across all 40 packages.

Suggested path forward

Smallest change that keeps the value of this PR:

  1. Mark sleepWinsRaceWorkflow with test.fails(...) and add a code comment linking to an issue tracking the V2 race-semantics decision. That preserves the test as a regression detector — if the underlying behavior is ever fixed, the test will go red and force a follow-up.
  2. Optional: open a separate small skill PR after clarifying scope, or move the skill to a personal location.
  3. Land stepWinsRaceWorkflow as a regular passing test — it's a useful regression check on the easy direction.

Happy to re-review once the failing assertion is addressed.

test('sleepWinsRaceWorkflow', { timeout: 60_000 }, async () => {
const run = await start(await e2e('sleepWinsRaceWorkflow'), []);
const returnValue = await run.returnValue;
expect(returnValue.winner).toBe('sleep');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocker — every e2e CI job is failing because of this assertion.

This test fails on every framework's e2e run (express, hono, nitro, fastify, astro, vite, nuxt, sveltekit, tanstack-start, both Next.js variants — local-dev, local-prod, local-postgres, AND vercel-prod) because 99_e2e.ts is shared across all workbenches via symlink. As of the latest run, that's 56+ failed jobs all on expected 'step' to be 'sleep'.

For example: E2E Local Dev Tests (express - stable)FAIL packages/core/e2e/e2e.test.ts > e2e > sleepWinsRaceWorkflow / AssertionError: expected 'step' to be 'sleep'.

The PR description acknowledges this is intentional and wants discussion before merge. That's reasonable as a discussion vehicle, but it can't merge in this state — the PR effectively makes the entire e2e matrix red for any other PR that bases on this one.

Until the underlying behavior question is settled, please pick one:

  1. test.fails(...) — vitest will pass when the assertion fails, and flip to red if/when the runtime is fixed. Cleanly documents the known-broken behavior.
  2. test.skip(...) with a TODO comment linking to the discussion / issue tracking the race semantics decision.
  3. Drop the failing test from this PR and land just stepWinsRaceWorkflow (which is the meaningful regression-prevention test); open a follow-up issue or PR for the slow-step-vs-fast-sleep case once the semantics are decided.

For reference, the underlying cause is consistent with the V2 inline-execution model in packages/core/src/runtime.ts (workflowEntrypoint): the handler executes the step inline rather than enqueuing it, so a 10s step blocks the handler for 10s and prevents the 1s sleep timer from firing on time. That's a real semantics question worth resolving — but in a separate change.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Resolved — merged latest main, which carried PR #1924's fix for the inline-execution behavior and added these same race tests directly. The duplicate set landed by this branch was dropped (commit 031e4b8), so 99_e2e.ts now matches main exactly and the race tests are no longer part of this PR's diff. Both sleepWinsRaceWorkflow and stepWinsRaceWorkflow now run from main's definitions and pass. This PR is dev-tooling only after the rebase.

Comment threadskills/dev-tmux/SKILL.md Outdated
@@ -0,0 +1,125 @@
---
name: dev-tmux
description: Spin up a 3-pane tmux session for local Workflow SDK development — Next.js turbopack workbench, observability UI, and a scratchpad — all routed through portless so each worktree gets isolated `.localhost` URLs. Use when the user asks to "spin up the dev session", "start dev mode", "set up the workbench", or any time they want to test workflows interactively in a worktree alongside the observability UI.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Activation phrases here are very generic — "spin up the dev session", "start dev mode", "set up the workbench" all describe the general task most contributors do daily, not the specific portless+tmux setup this skill describes. As written, this skill will activate for any contributor asking how to start the dev server, then drive them toward installing portless and adopting a personal tmux layout that the rest of the team doesn't use.

A couple of options:

  1. Narrow the activation phrases to portless-specific terms: "portless dev session", "tmux dev layout", "worktree-isolated dev URLs", etc. That way the skill only fires for users who already know they want this specific tooling.
  2. Move it out of the shared skills/ directory. This appears to be one developer's personal workflow tooling rather than a project-wide convention — skills/migrating-to-workflow-sdk/ is documenting a user-facing capability, whereas this is documenting an internal contributor's local-dev preference. A user-level agent skill (e.g. ~/.agents/skills/dev-tmux/) would keep it personal without affecting other contributors.

(The skill itself is well-written and the troubleshooting section is genuinely useful — this is purely about scope/activation, not quality.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Tightened in 5a44360 (v1.1 of the skill). The description now opens with Spin up a portless + tmux dev session ... and explicitly lists the only triggers as portless dev session / tmux dev layout for workflow / worktree-isolated dev URLs / wanting to wire URLs into the Claude statusline. It also explicitly says not to activate for the generic start the dev server / run pnpm dev task. Should now stay out of any contributor's path who isn't already opting into this tooling.

Kept it under skills/ rather than moving to ~/.claude/ because v1.1 also adds a statusline.sh helper script that needs to live next to the skill — moving it out would split the two artifacts. Open to revisiting if option 2 still feels right.

Comment threadworkbench/nextjs-turbopack/next.config.ts
Worktrees get deleted, so wiring the statusline to a worktree path
breaks the moment the worktree is removed. Update the skill and the
script header to recommend pointing `statusLine.command` at the
primary checkout (`$HOME/github/vercel/workflow/...`). The script
itself is already worktree-aware via Claude's `workspace.current_dir`
stdin JSON, so the same invocation surfaces routes for whichever
worktree the session is in.
Bump version to 1.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment threadskills/dev-tmux/SKILL.md Outdated
Comment on lines +5 to +6
author: Vercel Inc.
version: '1.2'

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

change the author to "Pranay Prakash" and set the version to 0.1 for this first release (since it's a new skill anyway)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 8b7225bmetadata.author: Pranay Prakash, metadata.version: '0.1'.

Comment threadskills/dev-tmux/SKILL.md Outdated
@@ -0,0 +1,160 @@
---
name: dev-tmux

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Suggested change
name: dev-tmux
name: internal-dev-workbench

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Applied in 8b7225bgit mv skills/dev-tmux skills/internal-dev-workbench, plus the matching frontmatter name: change and updates to all internal references in SKILL.md and statusline.sh.

@pranaygp

Copy link
Copy Markdown
ContributorAuthor

making some changes to the skill and testing it a bit more. moving to draft for now

pranaygpand others added 4 commits May 5, 2026 10:50
- Statusline overlay now renders `[dev] · [obs] · tmux:<prefix>`,
with the bracketed labels emitted as OSC 8 hyperlinks (clickable in
any modern terminal) styled cyan + underline so they stand out.
Replaces the old long-URL form that was hard to scan and click.
- Add a tmux-session indicator: shown when a session named exactly the
worktree prefix exists (uses `tmux has-session -t =<prefix>` for
exact matching).
- Change the skill's tmux session naming convention from the fixed
`workflow-dev` to `<worktree-prefix>` (basename of the branch — same
string portless uses as the subdomain prefix). This lets the
statusline locate the session deterministically and lets multiple
worktrees run dev sessions concurrently without manual disambiguation.
- Bump skill to v1.3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the abbreviated `tmux:<prefix>` indicator with the full
copy-paste-ready `tmux attach -t <prefix>` invocation. Saves a step
when grabbing the session from another shell.
Bump skill to v1.4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop the dim styling that made the overlay hard to read; use bold
bright cyan + underline for links and bold bright green for the
tmux command.
- Add Nerd Font glyphs: for dev, for obs, for the
tmux copy-paste hint. Falls back to box-drawing if the font lacks
Nerd Font ranges; layout is unaffected.
- Visual differentiation: cyan + underline = clickable hyperlink;
green = copy this command.
Bump skill to v1.5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The copy glyph in `emit_tmux` was a literal Nerd Font byte embedded in
the printf string and got stripped during a prior rewrite. Promote all
three icons (rocket / graph / copy) to top-level shell variables that
use \uHHHH-equivalent UTF-8 escapes, so the source survives editor
round-trips that don't preserve Private Use Area code points.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pranaygp

Copy link
Copy Markdown
ContributorAuthor

Updates since the previous round of review replies — all in skills/dev-tmux/:

Statusline overhaul (commits c714162938c26e)

  • The statusline command should be wired against the primary checkout ($HOME/github/vercel/workflow/skills/dev-tmux/statusline.sh), not a worktree path that disappears on cleanup. The script remains worktree-aware via Claude's workspace.current_dir stdin JSON, so a single stable path correctly surfaces routes for whichever worktree the session is in.
  • Output format reworked from long URLs to compact OSC 8 hyperlinks: [rocket] dev · [graph] obs · [copy] tmux attach -t <prefix>. The bracketed parts are clickable in iTerm2 / Kitty / WezTerm / Terminal.app / Ghostty (cmd/ctrl-click).
  • Bold + bright styling — bold bright cyan + underline for the clickable links, bold bright green for the tmux attach command (visually distinguishes "click this" from "copy this"). Earlier dim styling was hard to read.
  • Nerd Font icons (octicon rocket, octicon graph, fa copy) defined as top-level shell variables using UTF-8 escapes so they survive editor round-trips that don't preserve PUA code points.

Tmux session naming convention (commit 431026c)

  • The skill now creates the session with <worktree-prefix> (basename of the branch — same string portless uses for the <prefix>.<name>.localhost subdomain) instead of the fixed workflow-dev. Lets multiple worktrees run dev sessions concurrently with no manual disambiguation, and lets the statusline locate the session deterministically via tmux has-session -t =<prefix>.
  • The statusline shows the full tmux attach -t <prefix> invocation (commit b0f373c) so it's directly copy-pasteable into another shell.

Skill version bumped to 1.5. Marking ready for review.

…set version
- Rename `skills/dev-tmux/` → `skills/internal-dev-workbench/` to make
the name self-explanatory about the skill's scope (an internal
contributor's local dev workbench, not a generic tmux helper).
- Author: Pranay Prakash. Version: 0.1 (first release of the skill).
- Update internal references in SKILL.md and statusline.sh accordingly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

4 participants

@pranaygp@TooTallNate@VaguelySerious
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n 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;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Add dev-tmux skill for portless+tmux local Workflow SDK dev - #1916

Merged
pranaygp merged 11 commits into
mainfrom
pgp/stepflow-test
May 5, 2026
Merged

Add dev-tmux skill for portless+tmux local Workflow SDK dev#1916
pranaygp merged 11 commits into
mainfrom
pgp/stepflow-test

Conversation

@pranaygp

@pranaygppranaygp commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • New dev-tmux skill documenting the 3-pane tmux + portless setup for local Workflow SDK development (turbopack workbench, observability UI scoped to that workbench, scratchpad shell — all worktree-isolated via portless's branch-prefixed .localhost URLs).
  • Adds allowedDevOrigins: ['turbopack.localhost', '*.turbopack.localhost'] to workbench/nextjs-turbopack/next.config.ts so portless-style URLs don't get blocked by Next's cross-origin protection in dev (currently floods the logs with HMR warnings).

What changed since the original PR

The original PR also added two race workflows (sleepWinsRaceWorkflow, stepWinsRaceWorkflow) and their e2e tests to surface a Promise.race-vs-replay semantics issue. Those landed independently in #1924 (with the underlying fix), so this branch was rebased onto main and the duplicate additions removed — the race tests now come from main. This PR is dev-tooling only.

Test plan

  • pnpm vitest run packages/core/e2e/e2e.test.ts -t WinsRaceWorkflow against the turbopack workbench (passes after rebase, since [core] V2: skip inline step execution when suspension also has a wait #1924's fix is on main)
  • Spin up the workbench by following skills/dev-tmux/SKILL.md and confirm both https://<branch>.turbopack.localhost (HMR works, no cross-origin warnings) and https://<branch>.workflow-obs.localhost (observability UI shows runs from the workbench)

🤖 Generated with Claude Code

Adds two race workflows (sleepWinsRaceWorkflow, stepWinsRaceWorkflow)
that exercise Promise.race between a step function and a sleep call.
The current `sleepWinsRaceWorkflow` test fails — surfacing how the
replay engine resolves a previously-completed step instantly while
sleep still has to elapse.
Also adds a `dev-tmux` skill that documents the 3-pane tmux + portless
setup for testing workflows interactively in a worktree alongside the
observability UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings May 4, 2026 11:48
@changeset-bot

changeset-botBot commented May 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8b7225b

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 May 4, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 4, 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🥇 Nitro0.031s (-27.8% 🟢)1.005s (~)0.974s101.00x
💻 LocalExpress0.034s (-22.3% 🟢)1.005s (~)0.970s101.11x
🐘 PostgresExpress0.046s (-20.7% 🟢)1.012s (~)0.966s101.48x
🐘 PostgresNitro0.046s (-51.7% 🟢)1.012s (-3.0%)0.966s101.48x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.315s (-23.1% 🟢)2.072s (-17.4% 🟢)1.758s101.00x
▲ VercelNext.js (Turbopack)0.793s (+215.3% 🔺)2.620s (+12.3% 🔺)1.827s102.52x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.068s (-5.1% 🟢)2.006s (~)0.938s101.00x
💻 LocalNitro1.073s (-5.2% 🟢)2.006s (~)0.933s101.00x
🐘 PostgresNitro1.083s (-5.0%)2.011s (~)0.927s101.01x
🐘 PostgresExpress1.085s (-5.4% 🟢)2.010s (~)0.925s101.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.777s (-54.3% 🟢)3.967s (-32.9% 🟢)2.190s101.00x
▲ VercelNext.js (Turbopack)2.267s (+11.4% 🔺)3.754s (-2.0%)1.487s101.28x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.402s (-4.8%)11.020s (~)0.618s31.00x
🐘 PostgresNitro10.402s (-4.3%)11.017s (~)0.615s31.00x
💻 LocalNitro10.414s (-4.9%)11.022s (~)0.607s31.00x
🐘 PostgresExpress10.446s (-4.7%)11.019s (~)0.574s31.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.463s (-43.3% 🟢)15.032s (-40.2% 🟢)1.568s21.00x
▲ VercelNext.js (Turbopack)14.098s (-18.6% 🟢)15.843s (-18.3% 🟢)1.745s21.05x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.436s (-10.2% 🟢)14.025s (-6.7% 🟢)0.589s51.00x
🐘 PostgresNitro13.445s (-7.9% 🟢)14.018s (-6.7% 🟢)0.573s51.00x
💻 LocalNitro13.481s (-10.5% 🟢)14.027s (-12.5% 🟢)0.546s51.00x
🐘 PostgresExpress13.493s (-7.5% 🟢)14.024s (-6.6% 🟢)0.531s51.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro21.085s (-67.3% 🟢)23.044s (-65.4% 🟢)1.959s31.00x
▲ VercelNext.js (Turbopack)22.401s (-57.4% 🟢)24.203s (-55.7% 🟢)1.802s31.06x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro11.880s (-14.9% 🟢)12.016s (-16.0% 🟢)0.136s81.00x
💻 LocalNitro11.939s (-28.9% 🟢)12.147s (-28.7% 🟢)0.208s81.00x
💻 LocalExpress11.974s (-27.9% 🟢)12.396s (-27.2% 🟢)0.422s81.01x
🐘 PostgresExpress12.130s (-13.4% 🟢)12.642s (-13.4% 🟢)0.512s81.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro30.301s (-92.8% 🟢)32.393s (-92.4% 🟢)2.092s31.00x
▲ VercelNext.js (Turbopack)34.399s (-91.3% 🟢)36.050s (-90.9% 🟢)1.651s31.14x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.156s (-22.3% 🟢)2.005s (~)0.849s151.00x
🐘 PostgresNitro1.159s (-9.1% 🟢)2.008s (~)0.849s151.00x
🐘 PostgresExpress1.160s (-8.0% 🟢)2.008s (~)0.848s151.00x
💻 LocalNitro1.180s (-27.6% 🟢)2.006s (-3.3%)0.825s151.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.412s (-14.4% 🟢)3.982s (-7.9% 🟢)1.570s81.00x
▲ VercelNext.js (Turbopack)4.906s (+44.4% 🔺)6.610s (+34.0% 🔺)1.704s52.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.228s (-48.0% 🟢)2.008s (-33.3% 🟢)0.780s151.00x
🐘 PostgresNitro1.238s (-47.3% 🟢)2.006s (-33.3% 🟢)0.768s151.01x
💻 LocalExpress1.598s (-45.9% 🟢)2.005s (-41.9% 🟢)0.407s151.30x
💻 LocalNitro1.894s (-39.8% 🟢)2.149s (-44.7% 🟢)0.256s141.54x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.300s (-18.6% 🟢)5.165s (-12.8% 🟢)1.865s61.00x
▲ VercelNext.js (Turbopack)5.590s (-21.3% 🟢)7.240s (-18.7% 🟢)1.651s51.69x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.380s (-60.4% 🟢)2.009s (-49.9% 🟢)0.629s151.00x
🐘 PostgresNitro1.390s (-60.0% 🟢)2.008s (-49.9% 🟢)0.617s151.01x
💻 LocalExpress4.503s (-46.0% 🟢)4.725s (-47.7% 🟢)0.222s73.26x
💻 LocalNitro5.496s (-34.2% 🟢)6.014s (-33.3% 🟢)0.518s53.98x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)5.990s (-32.8% 🟢)7.487s (-31.7% 🟢)1.496s51.00x
▲ VercelNitro6.899s (+95.7% 🔺)10.766s (+94.6% 🔺)3.867s31.15x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.159s (-7.8% 🟢)2.009s (~)0.850s151.00x
🐘 PostgresNitro1.165s (-7.4% 🟢)2.009s (~)0.844s151.00x
💻 LocalExpress1.317s (-30.5% 🟢)2.005s (-15.2% 🟢)0.689s151.14x
💻 LocalNitro1.415s (-24.1% 🟢)2.007s (-14.3% 🟢)0.591s151.22x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.462s (~)3.978s (-4.6%)1.516s81.00x
▲ VercelNext.js (Turbopack)5.008s (+70.8% 🔺)6.602s (+42.2% 🔺)1.594s52.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.221s (-47.8% 🟢)2.009s (-33.3% 🟢)0.788s151.00x
🐘 PostgresExpress1.241s (-47.0% 🟢)2.008s (-33.3% 🟢)0.767s151.02x
💻 LocalExpress1.666s (-46.8% 🟢)2.005s (-46.7% 🟢)0.339s151.36x
💻 LocalNitro2.096s (-31.6% 🟢)2.592s (-33.3% 🟢)0.496s121.72x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.255s (+0.7%)5.219s (+2.8%)1.965s61.00x
▲ VercelNext.js (Turbopack)4.456s (+41.8% 🔺)6.286s (+39.0% 🔺)1.830s51.37x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.358s (-61.0% 🟢)2.007s (-49.9% 🟢)0.649s151.00x
🐘 PostgresExpress1.379s (-60.6% 🟢)2.008s (-49.9% 🟢)0.628s151.02x
💻 LocalExpress4.938s (-43.9% 🟢)5.513s (-40.6% 🟢)0.575s63.64x
💻 LocalNitro6.426s (-29.7% 🟢)6.819s (-32.0% 🟢)0.393s54.73x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.698s (-7.8% 🟢)6.786s (~)2.088s51.00x
▲ VercelNext.js (Turbopack)5.481s (-18.9% 🟢)7.171s (-16.1% 🟢)1.689s51.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.429s (-47.8% 🟢)1.006s (~)0.578s601.00x
🐘 PostgresExpress0.435s (-48.2% 🟢)1.007s (-1.6%)0.572s601.01x
💻 LocalExpress0.444s (-54.8% 🟢)1.004s (-6.7% 🟢)0.559s601.04x
💻 LocalNitro0.471s (-52.0% 🟢)1.004s (-8.2% 🟢)0.534s601.10x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.925s (-77.7% 🟢)7.020s (-70.8% 🟢)2.095s91.00x
▲ VercelNext.js (Turbopack)6.772s (-53.3% 🟢)8.207s (-49.0% 🟢)1.435s81.37x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.028s (-46.7% 🟢)1.413s (-32.7% 🟢)0.384s641.00x
🐘 PostgresExpress1.065s (-46.1% 🟢)1.771s (-21.5% 🟢)0.706s511.04x
💻 LocalExpress1.125s (-62.7% 🟢)2.005s (-44.1% 🟢)0.880s451.09x
💻 LocalNitro1.184s (-61.0% 🟢)2.006s (-46.6% 🟢)0.822s451.15x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro12.382s (-68.6% 🟢)14.665s (-64.5% 🟢)2.283s71.00x
▲ VercelNext.js (Turbopack)15.639s (-68.6% 🟢)17.695s (-65.8% 🟢)2.056s61.26x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.974s (-51.9% 🟢)2.380s (-48.3% 🟢)0.405s511.00x
🐘 PostgresExpress2.076s (-48.0% 🟢)2.529s (-42.1% 🟢)0.453s481.05x
💻 LocalExpress2.545s (-72.4% 🟢)3.032s (-69.7% 🟢)0.488s401.29x
💻 LocalNitro2.664s (-71.4% 🟢)3.057s (-69.5% 🟢)0.394s401.35x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro38.936s (-59.8% 🟢)41.471s (-57.9% 🟢)2.535s41.00x
▲ VercelNext.js (Turbopack)45.359s (-57.7% 🟢)47.056s (-56.8% 🟢)1.697s31.16x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.186s (-34.3% 🟢)1.005s (~)0.819s601.00x
🐘 PostgresExpress0.200s (-29.0% 🟢)1.006s (~)0.806s601.08x
💻 LocalExpress0.389s (-30.6% 🟢)1.004s (~)0.615s602.09x
💻 LocalNitro0.441s (-27.1% 🟢)1.004s (-1.7%)0.563s602.37x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.370s (+42.7% 🔺)3.938s (+17.5% 🔺)1.568s161.00x
▲ VercelNext.js (Turbopack)3.739s (+84.9% 🔺)5.211s (+37.4% 🔺)1.473s121.58x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.307s (-38.1% 🟢)1.006s (~)0.699s901.00x
🐘 PostgresExpress0.329s (-35.5% 🟢)1.006s (~)0.678s901.07x
💻 LocalExpress2.111s (-16.0% 🟢)2.684s (-10.8% 🟢)0.573s346.88x
💻 LocalNitro2.201s (-13.3% 🟢)2.945s (-2.1%)0.744s317.17x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.219s (+123.8% 🔺)9.007s (+86.8% 🔺)1.788s101.00x
▲ VercelNext.js (Turbopack)7.230s (+104.5% 🔺)8.888s (+71.1% 🔺)1.657s111.00x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.623s (-21.2% 🟢)1.006s (~)0.383s1201.00x
🐘 PostgresExpress0.676s (-17.4% 🟢)1.007s (-1.1%)0.330s1201.09x
💻 LocalExpress8.906s (-20.4% 🟢)9.485s (-20.6% 🟢)0.579s1314.30x
💻 LocalNitro10.253s (-8.4% 🟢)10.694s (-8.3% 🟢)0.441s1216.47x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)22.466s (+117.5% 🔺)23.969s (+95.1% 🔺)1.504s61.00x
▲ VercelNitro24.505s (+217.3% 🔺)26.705s (+184.1% 🔺)2.200s51.09x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.120s (+462.6% 🔺)2.004s (+99.5% 🔺)0.009s (-22.3% 🟢)2.016s (+98.0% 🔺)0.896s101.00x
💻 LocalNitro1.134s (+430.7% 🔺)2.006s (+99.7% 🔺)0.012s (-0.8%)2.020s (+98.3% 🔺)0.886s101.01x
🐘 PostgresNitro1.140s (+456.2% 🔺)2.002s (+100.3% 🔺)0.001s (-13.3% 🟢)2.011s (+98.8% 🔺)0.871s101.02x
🐘 PostgresExpress1.159s (+465.2% 🔺)1.998s (+100.1% 🔺)0.001s (-12.5% 🟢)2.011s (+98.8% 🔺)0.852s101.03x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.439s (-36.4% 🟢)3.535s (-33.0% 🟢)2.339s (+215.2% 🔺)6.477s (~)4.038s101.00x
▲ VercelNext.js (Turbopack)4.213s (-38.5% 🟢)4.295s (-50.3% 🟢)1.310s (+107.3% 🔺)7.294s (-25.5% 🟢)3.081s101.73x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.488s (+138.4% 🔺)2.003s (+99.0% 🔺)0.004s (-3.3%)2.025s (+98.0% 🔺)0.536s301.00x
💻 LocalNitro1.523s (+81.6% 🔺)2.012s (+98.8% 🔺)0.010s (+5.3% 🔺)2.023s (+81.3% 🔺)0.500s301.02x
🐘 PostgresExpress1.540s (+144.4% 🔺)2.009s (+99.6% 🔺)0.004s (+3.6%)2.026s (+98.0% 🔺)0.486s301.03x
💻 LocalExpress1.665s (+119.9% 🔺)2.009s (+95.2% 🔺)0.009s (-1.0%)2.199s (+111.5% 🔺)0.534s281.12x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.887s (-80.0% 🟢)7.416s (-75.9% 🟢)0.184s (+64.0% 🔺)8.100s (-74.5% 🟢)2.213s81.00x
▲ VercelNext.js (Turbopack)13.623s (-19.5% 🟢)13.512s (-25.9% 🟢)0.199s (-5.7% 🟢)15.368s (-18.8% 🟢)1.744s42.31x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.653s (-32.6% 🟢)1.050s (-15.9% 🟢)0.000s (-57.9% 🟢)1.060s (-15.7% 🟢)0.407s571.00x
🐘 PostgresExpress0.672s (-30.0% 🟢)1.033s (-19.2% 🟢)0.000s (+19.0% 🔺)1.042s (-20.2% 🟢)0.370s581.03x
💻 LocalExpress1.274s (+4.0%)2.013s (~)0.000s (-40.0% 🟢)2.015s (~)0.740s301.95x
💻 LocalNitro1.310s (+7.1% 🔺)2.017s (~)0.001s (+400.0% 🔺)2.019s (~)0.709s302.01x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.313s (+8.6% 🔺)4.592s (+4.5%)0.000s (+8.3% 🔺)5.023s (+4.5%)1.710s121.00x
▲ VercelNext.js (Turbopack)5.512s (-45.9% 🟢)6.062s (-47.4% 🟢)0.000s (+Infinity% 🔺)6.982s (-42.1% 🟢)1.470s91.66x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.168s (-34.8% 🟢)1.996s (-6.8% 🟢)0.000s (-100.0% 🟢)2.026s (-6.8% 🟢)0.858s301.00x
🐘 PostgresExpress1.377s (-22.3% 🟢)2.066s (-5.1% 🟢)0.000s (NaN%)2.082s (-5.3% 🟢)0.705s291.18x
💻 LocalExpress2.968s (-14.4% 🟢)3.555s (-11.9% 🟢)0.000s (-70.6% 🟢)3.559s (-11.8% 🟢)0.591s172.54x
💻 LocalNitro3.084s (-9.0% 🟢)3.781s (-6.2% 🟢)0.000s (-29.7% 🟢)3.783s (-6.3% 🟢)0.700s162.64x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.124s (+25.2% 🔺)6.356s (+18.3% 🔺)0.000s (-100.0% 🟢)6.813s (+17.6% 🔺)1.689s91.00x
▲ VercelNext.js (Turbopack)7.535s (+34.2% 🔺)8.909s (+27.6% 🔺)0.000s (-100.0% 🟢)9.344s (+23.9% 🔺)1.809s71.47x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress18/21
🐘 PostgresNitro17/21
▲ VercelNitro19/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

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

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actionsBot commented May 4, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production92502191144
✅ 💻 Local Development123702191456
✅ 📦 Local Production123702191456
✅ 🐘 Local Postgres123702191456
✅ 🪟 Windows10400104
✅ 📋 Other5520176728
Total5292010526344

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro78026
✅ example78026
✅ express78026
✅ fastify78026
✅ hono78026
✅ nextjs-turbopack10202
✅ nextjs-webpack10202
✅ nitro78026
✅ nuxt78026
✅ sveltekit9707
✅ vite78026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack10400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable79025
✅ e2e-local-dev-tanstack-start-79025
✅ e2e-local-postgres-nest-stable79025
✅ e2e-local-postgres-tanstack-start-79025
✅ e2e-local-prod-nest-stable79025
✅ e2e-local-prod-tanstack-start-79025
✅ e2e-vercel-prod-tanstack-start78026

📋 View full workflow run

Adds allowedDevOrigins entries so portless-style worktree-prefixed
.localhost URLs (e.g. https://<branch>.turbopack.localhost) can hit
HMR and dev-only endpoints without Next's cross-origin protection
flooding the logs with warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

CopilotAI 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.

Pull request overview

Adds new end-to-end coverage for Promise.race behavior between a long-running step and sleep(), and introduces a new dev-tmux skill to standardize a local 3-pane dev setup (workbench + observability UI + scratchpad) using portless for worktree-isolated .localhost URLs.

Changes:

  • Add sleepWinsRaceWorkflow / stepWinsRaceWorkflow workflows to the workbench e2e workflow collection.
  • Add corresponding Vitest e2e tests asserting the expected race winner and duration bounds.
  • Add skills/dev-tmux/SKILL.md documentation for a tmux + portless local dev workflow; add a (currently empty) changeset file.

Reviewed changes

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

FileDescription
workbench/example/workflows/99_e2e.tsAdds two new workflows plus a shared step helper to exercise Promise.race between a step and sleep().
packages/core/e2e/e2e.test.tsAdds two e2e tests covering the new race workflows and asserting winner + duration.
skills/dev-tmux/SKILL.mdNew skill documenting a portless-routed 3-pane tmux setup for local SDK dev + e2e runs.
.changeset/better-pets-reply.mdAdds a new changeset file, but it is currently empty/invalid.

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

Comment on lines +584 to +590
test('sleepWinsRaceWorkflow', { timeout: 60_000 }, async () => {
const run = await start(await e2e('sleepWinsRaceWorkflow'), []);
const returnValue = await run.returnValue;
expect(returnValue.winner).toBe('sleep');
// Sleep is 1s; step would take 10s. Should resolve in ~1s, well under 5s.
expect(returnValue.durationMs).toBeLessThan(5_000);
});

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Obsolete after rebase onto main — PR #1924 fixed the underlying race semantics and added these same tests directly to main. The duplicate set on this branch was dropped (031e4b8); the race tests are no longer part of this PR's diff.

Comment thread.changeset/better-pets-reply.md
Comment threadskills/dev-tmux/SKILL.md Outdated
Comment on lines +51 to +59
tmux send-keys -t "$SESSION".1 \
'cd workbench/nextjs-turbopack && WORKFLOW_PUBLIC_MANIFEST=1 portless run --name turbopack pnpm dev' C-m

# Pane 2 (top-right): observability UI scoped to the workbench app
tmux send-keys -t "$SESSION".2 \
'cd workbench/nextjs-turbopack && portless run --name workflow-obs sh -c "pnpm workflow web --webPort \$PORT --noBrowser"' C-m

# Pane 3 (bottom-right): scratchpad at repo root
tmux send-keys -t "$SESSION".3 'echo "scratchpad: $(pwd)"' C-m

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Fixed in 5a44360. The skill now captures each pane's ID at split time with -P -F '#{pane_id}' and uses those IDs as targets, so it's correct under both pane-base-index 0 and pane-base-index 1 regardless of the user's tmux config.

Comment threadpackages/core/e2e/e2e.test.ts
TooTallNate
TooTallNate previously requested changes May 4, 2026

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verdict: Request Changes

The PR has two things in it: e2e race tests (good idea, surfacing a real V2-handler semantics question) and a personal-dev skill file. I have one blocker on the test, one scope concern on the skill, and one minor nit on the next.config change.


Summary of inline comments

  1. packages/core/e2e/e2e.test.ts:587 — Blocker.sleepWinsRaceWorkflow fails on every e2e job (56+ jobs across all frameworks × all environments). Needs test.fails(...), .skip with TODO, or removal until the underlying race semantics are decided. The PR description correctly identifies this as intentional pending discussion, but the current state makes the e2e matrix unusable for any PR based on this one.

  2. skills/dev-tmux/SKILL.md:3 — Scope. Activation phrases ("spin up the dev session", "start dev mode", "set up the workbench") will trigger for general contributor questions, then push them toward installing portless. Either narrow the phrases to portless-specific terms or move the skill to a user-level location.

  3. workbench/nextjs-turbopack/next.config.ts:13 — Nit. Hardcoding *.turbopack.localhost in the workbench bakes one developer's convention into the shared config. Dev-only, so harmless — flagging for awareness, not blocking.


What I verified

  • Diagnosis is correct: The V2 inline-execution model in workflowEntrypoint (packages/core/src/runtime.ts) executes step bodies inline rather than enqueueing them. A 10-second delayMsStep blocks the handler for 10 seconds, which prevents the 1-second sleep('1s') timer from firing on time. So Promise.race ends up resolving with 'step' because that's what the handler delivers first. Whether this should be considered correct behavior or a bug is a real question.
  • Symlinks: Confirmed 99_e2e.ts is symlinked from workbench/example/workflows/99_e2e.ts into all other workbenches per AGENTS.md convention. ✅
  • Empty changeset: ---\n--- is the right pattern for tests-only PRs that don't ship package changes; matches existing examples (fix-vite-step-source-maps-e2e.md, tanstack-start-workbench.md). ✅
  • Skill frontmatter: version: '1.0' is fine for a new skill (existing migrating-to-workflow-sdk is '0.2.0'). Format matches.
  • Typecheck: Clean across all 40 packages.

Suggested path forward

Smallest change that keeps the value of this PR:

  1. Mark sleepWinsRaceWorkflow with test.fails(...) and add a code comment linking to an issue tracking the V2 race-semantics decision. That preserves the test as a regression detector — if the underlying behavior is ever fixed, the test will go red and force a follow-up.
  2. Optional: open a separate small skill PR after clarifying scope, or move the skill to a personal location.
  3. Land stepWinsRaceWorkflow as a regular passing test — it's a useful regression check on the easy direction.

Happy to re-review once the failing assertion is addressed.

test('sleepWinsRaceWorkflow', { timeout: 60_000 }, async () => {
const run = await start(await e2e('sleepWinsRaceWorkflow'), []);
const returnValue = await run.returnValue;
expect(returnValue.winner).toBe('sleep');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocker — every e2e CI job is failing because of this assertion.

This test fails on every framework's e2e run (express, hono, nitro, fastify, astro, vite, nuxt, sveltekit, tanstack-start, both Next.js variants — local-dev, local-prod, local-postgres, AND vercel-prod) because 99_e2e.ts is shared across all workbenches via symlink. As of the latest run, that's 56+ failed jobs all on expected 'step' to be 'sleep'.

For example: E2E Local Dev Tests (express - stable)FAIL packages/core/e2e/e2e.test.ts > e2e > sleepWinsRaceWorkflow / AssertionError: expected 'step' to be 'sleep'.

The PR description acknowledges this is intentional and wants discussion before merge. That's reasonable as a discussion vehicle, but it can't merge in this state — the PR effectively makes the entire e2e matrix red for any other PR that bases on this one.

Until the underlying behavior question is settled, please pick one:

  1. test.fails(...) — vitest will pass when the assertion fails, and flip to red if/when the runtime is fixed. Cleanly documents the known-broken behavior.
  2. test.skip(...) with a TODO comment linking to the discussion / issue tracking the race semantics decision.
  3. Drop the failing test from this PR and land just stepWinsRaceWorkflow (which is the meaningful regression-prevention test); open a follow-up issue or PR for the slow-step-vs-fast-sleep case once the semantics are decided.

For reference, the underlying cause is consistent with the V2 inline-execution model in packages/core/src/runtime.ts (workflowEntrypoint): the handler executes the step inline rather than enqueuing it, so a 10s step blocks the handler for 10s and prevents the 1s sleep timer from firing on time. That's a real semantics question worth resolving — but in a separate change.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Resolved — merged latest main, which carried PR #1924's fix for the inline-execution behavior and added these same race tests directly. The duplicate set landed by this branch was dropped (commit 031e4b8), so 99_e2e.ts now matches main exactly and the race tests are no longer part of this PR's diff. Both sleepWinsRaceWorkflow and stepWinsRaceWorkflow now run from main's definitions and pass. This PR is dev-tooling only after the rebase.

Comment threadskills/dev-tmux/SKILL.md Outdated
@@ -0,0 +1,125 @@
---
name: dev-tmux
description: Spin up a 3-pane tmux session for local Workflow SDK development — Next.js turbopack workbench, observability UI, and a scratchpad — all routed through portless so each worktree gets isolated `.localhost` URLs. Use when the user asks to "spin up the dev session", "start dev mode", "set up the workbench", or any time they want to test workflows interactively in a worktree alongside the observability UI.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Activation phrases here are very generic — "spin up the dev session", "start dev mode", "set up the workbench" all describe the general task most contributors do daily, not the specific portless+tmux setup this skill describes. As written, this skill will activate for any contributor asking how to start the dev server, then drive them toward installing portless and adopting a personal tmux layout that the rest of the team doesn't use.

A couple of options:

  1. Narrow the activation phrases to portless-specific terms: "portless dev session", "tmux dev layout", "worktree-isolated dev URLs", etc. That way the skill only fires for users who already know they want this specific tooling.
  2. Move it out of the shared skills/ directory. This appears to be one developer's personal workflow tooling rather than a project-wide convention — skills/migrating-to-workflow-sdk/ is documenting a user-facing capability, whereas this is documenting an internal contributor's local-dev preference. A user-level agent skill (e.g. ~/.agents/skills/dev-tmux/) would keep it personal without affecting other contributors.

(The skill itself is well-written and the troubleshooting section is genuinely useful — this is purely about scope/activation, not quality.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Tightened in 5a44360 (v1.1 of the skill). The description now opens with Spin up a portless + tmux dev session ... and explicitly lists the only triggers as portless dev session / tmux dev layout for workflow / worktree-isolated dev URLs / wanting to wire URLs into the Claude statusline. It also explicitly says not to activate for the generic start the dev server / run pnpm dev task. Should now stay out of any contributor's path who isn't already opting into this tooling.

Kept it under skills/ rather than moving to ~/.claude/ because v1.1 also adds a statusline.sh helper script that needs to live next to the skill — moving it out would split the two artifacts. Open to revisiting if option 2 still feels right.

Comment threadworkbench/nextjs-turbopack/next.config.ts
Worktrees get deleted, so wiring the statusline to a worktree path
breaks the moment the worktree is removed. Update the skill and the
script header to recommend pointing `statusLine.command` at the
primary checkout (`$HOME/github/vercel/workflow/...`). The script
itself is already worktree-aware via Claude's `workspace.current_dir`
stdin JSON, so the same invocation surfaces routes for whichever
worktree the session is in.
Bump version to 1.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment threadskills/dev-tmux/SKILL.md Outdated
Comment on lines +5 to +6
author: Vercel Inc.
version: '1.2'

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

change the author to "Pranay Prakash" and set the version to 0.1 for this first release (since it's a new skill anyway)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 8b7225bmetadata.author: Pranay Prakash, metadata.version: '0.1'.

Comment threadskills/dev-tmux/SKILL.md Outdated
@@ -0,0 +1,160 @@
---
name: dev-tmux

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Suggested change
name: dev-tmux
name: internal-dev-workbench

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Applied in 8b7225bgit mv skills/dev-tmux skills/internal-dev-workbench, plus the matching frontmatter name: change and updates to all internal references in SKILL.md and statusline.sh.

@pranaygp

Copy link
Copy Markdown
ContributorAuthor

making some changes to the skill and testing it a bit more. moving to draft for now

pranaygpand others added 4 commits May 5, 2026 10:50
- Statusline overlay now renders `[dev] · [obs] · tmux:<prefix>`,
with the bracketed labels emitted as OSC 8 hyperlinks (clickable in
any modern terminal) styled cyan + underline so they stand out.
Replaces the old long-URL form that was hard to scan and click.
- Add a tmux-session indicator: shown when a session named exactly the
worktree prefix exists (uses `tmux has-session -t =<prefix>` for
exact matching).
- Change the skill's tmux session naming convention from the fixed
`workflow-dev` to `<worktree-prefix>` (basename of the branch — same
string portless uses as the subdomain prefix). This lets the
statusline locate the session deterministically and lets multiple
worktrees run dev sessions concurrently without manual disambiguation.
- Bump skill to v1.3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the abbreviated `tmux:<prefix>` indicator with the full
copy-paste-ready `tmux attach -t <prefix>` invocation. Saves a step
when grabbing the session from another shell.
Bump skill to v1.4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop the dim styling that made the overlay hard to read; use bold
bright cyan + underline for links and bold bright green for the
tmux command.
- Add Nerd Font glyphs: for dev, for obs, for the
tmux copy-paste hint. Falls back to box-drawing if the font lacks
Nerd Font ranges; layout is unaffected.
- Visual differentiation: cyan + underline = clickable hyperlink;
green = copy this command.
Bump skill to v1.5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The copy glyph in `emit_tmux` was a literal Nerd Font byte embedded in
the printf string and got stripped during a prior rewrite. Promote all
three icons (rocket / graph / copy) to top-level shell variables that
use \uHHHH-equivalent UTF-8 escapes, so the source survives editor
round-trips that don't preserve Private Use Area code points.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pranaygp

Copy link
Copy Markdown
ContributorAuthor

Updates since the previous round of review replies — all in skills/dev-tmux/:

Statusline overhaul (commits c714162938c26e)

  • The statusline command should be wired against the primary checkout ($HOME/github/vercel/workflow/skills/dev-tmux/statusline.sh), not a worktree path that disappears on cleanup. The script remains worktree-aware via Claude's workspace.current_dir stdin JSON, so a single stable path correctly surfaces routes for whichever worktree the session is in.
  • Output format reworked from long URLs to compact OSC 8 hyperlinks: [rocket] dev · [graph] obs · [copy] tmux attach -t <prefix>. The bracketed parts are clickable in iTerm2 / Kitty / WezTerm / Terminal.app / Ghostty (cmd/ctrl-click).
  • Bold + bright styling — bold bright cyan + underline for the clickable links, bold bright green for the tmux attach command (visually distinguishes "click this" from "copy this"). Earlier dim styling was hard to read.
  • Nerd Font icons (octicon rocket, octicon graph, fa copy) defined as top-level shell variables using UTF-8 escapes so they survive editor round-trips that don't preserve PUA code points.

Tmux session naming convention (commit 431026c)

  • The skill now creates the session with <worktree-prefix> (basename of the branch — same string portless uses for the <prefix>.<name>.localhost subdomain) instead of the fixed workflow-dev. Lets multiple worktrees run dev sessions concurrently with no manual disambiguation, and lets the statusline locate the session deterministically via tmux has-session -t =<prefix>.
  • The statusline shows the full tmux attach -t <prefix> invocation (commit b0f373c) so it's directly copy-pasteable into another shell.

Skill version bumped to 1.5. Marking ready for review.

…set version
- Rename `skills/dev-tmux/` → `skills/internal-dev-workbench/` to make
the name self-explanatory about the skill's scope (an internal
contributor's local dev workbench, not a generic tmux helper).
- Author: Pranay Prakash. Version: 0.1 (first release of the skill).
- Update internal references in SKILL.md and statusline.sh accordingly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

4 participants

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

Add dev-tmux skill for portless+tmux local Workflow SDK dev - #1916

Merged
pranaygp merged 11 commits into
mainfrom
pgp/stepflow-test
May 5, 2026
Merged

Add dev-tmux skill for portless+tmux local Workflow SDK dev#1916
pranaygp merged 11 commits into
mainfrom
pgp/stepflow-test

Conversation

@pranaygp

@pranaygppranaygp commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • New dev-tmux skill documenting the 3-pane tmux + portless setup for local Workflow SDK development (turbopack workbench, observability UI scoped to that workbench, scratchpad shell — all worktree-isolated via portless's branch-prefixed .localhost URLs).
  • Adds allowedDevOrigins: ['turbopack.localhost', '*.turbopack.localhost'] to workbench/nextjs-turbopack/next.config.ts so portless-style URLs don't get blocked by Next's cross-origin protection in dev (currently floods the logs with HMR warnings).

What changed since the original PR

The original PR also added two race workflows (sleepWinsRaceWorkflow, stepWinsRaceWorkflow) and their e2e tests to surface a Promise.race-vs-replay semantics issue. Those landed independently in #1924 (with the underlying fix), so this branch was rebased onto main and the duplicate additions removed — the race tests now come from main. This PR is dev-tooling only.

Test plan

  • pnpm vitest run packages/core/e2e/e2e.test.ts -t WinsRaceWorkflow against the turbopack workbench (passes after rebase, since [core] V2: skip inline step execution when suspension also has a wait #1924's fix is on main)
  • Spin up the workbench by following skills/dev-tmux/SKILL.md and confirm both https://<branch>.turbopack.localhost (HMR works, no cross-origin warnings) and https://<branch>.workflow-obs.localhost (observability UI shows runs from the workbench)

🤖 Generated with Claude Code

Adds two race workflows (sleepWinsRaceWorkflow, stepWinsRaceWorkflow)
that exercise Promise.race between a step function and a sleep call.
The current `sleepWinsRaceWorkflow` test fails — surfacing how the
replay engine resolves a previously-completed step instantly while
sleep still has to elapse.
Also adds a `dev-tmux` skill that documents the 3-pane tmux + portless
setup for testing workflows interactively in a worktree alongside the
observability UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings May 4, 2026 11:48
@changeset-bot

changeset-botBot commented May 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8b7225b

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 May 4, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 4, 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🥇 Nitro0.031s (-27.8% 🟢)1.005s (~)0.974s101.00x
💻 LocalExpress0.034s (-22.3% 🟢)1.005s (~)0.970s101.11x
🐘 PostgresExpress0.046s (-20.7% 🟢)1.012s (~)0.966s101.48x
🐘 PostgresNitro0.046s (-51.7% 🟢)1.012s (-3.0%)0.966s101.48x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.315s (-23.1% 🟢)2.072s (-17.4% 🟢)1.758s101.00x
▲ VercelNext.js (Turbopack)0.793s (+215.3% 🔺)2.620s (+12.3% 🔺)1.827s102.52x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.068s (-5.1% 🟢)2.006s (~)0.938s101.00x
💻 LocalNitro1.073s (-5.2% 🟢)2.006s (~)0.933s101.00x
🐘 PostgresNitro1.083s (-5.0%)2.011s (~)0.927s101.01x
🐘 PostgresExpress1.085s (-5.4% 🟢)2.010s (~)0.925s101.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.777s (-54.3% 🟢)3.967s (-32.9% 🟢)2.190s101.00x
▲ VercelNext.js (Turbopack)2.267s (+11.4% 🔺)3.754s (-2.0%)1.487s101.28x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.402s (-4.8%)11.020s (~)0.618s31.00x
🐘 PostgresNitro10.402s (-4.3%)11.017s (~)0.615s31.00x
💻 LocalNitro10.414s (-4.9%)11.022s (~)0.607s31.00x
🐘 PostgresExpress10.446s (-4.7%)11.019s (~)0.574s31.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.463s (-43.3% 🟢)15.032s (-40.2% 🟢)1.568s21.00x
▲ VercelNext.js (Turbopack)14.098s (-18.6% 🟢)15.843s (-18.3% 🟢)1.745s21.05x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.436s (-10.2% 🟢)14.025s (-6.7% 🟢)0.589s51.00x
🐘 PostgresNitro13.445s (-7.9% 🟢)14.018s (-6.7% 🟢)0.573s51.00x
💻 LocalNitro13.481s (-10.5% 🟢)14.027s (-12.5% 🟢)0.546s51.00x
🐘 PostgresExpress13.493s (-7.5% 🟢)14.024s (-6.6% 🟢)0.531s51.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro21.085s (-67.3% 🟢)23.044s (-65.4% 🟢)1.959s31.00x
▲ VercelNext.js (Turbopack)22.401s (-57.4% 🟢)24.203s (-55.7% 🟢)1.802s31.06x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro11.880s (-14.9% 🟢)12.016s (-16.0% 🟢)0.136s81.00x
💻 LocalNitro11.939s (-28.9% 🟢)12.147s (-28.7% 🟢)0.208s81.00x
💻 LocalExpress11.974s (-27.9% 🟢)12.396s (-27.2% 🟢)0.422s81.01x
🐘 PostgresExpress12.130s (-13.4% 🟢)12.642s (-13.4% 🟢)0.512s81.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro30.301s (-92.8% 🟢)32.393s (-92.4% 🟢)2.092s31.00x
▲ VercelNext.js (Turbopack)34.399s (-91.3% 🟢)36.050s (-90.9% 🟢)1.651s31.14x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.156s (-22.3% 🟢)2.005s (~)0.849s151.00x
🐘 PostgresNitro1.159s (-9.1% 🟢)2.008s (~)0.849s151.00x
🐘 PostgresExpress1.160s (-8.0% 🟢)2.008s (~)0.848s151.00x
💻 LocalNitro1.180s (-27.6% 🟢)2.006s (-3.3%)0.825s151.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.412s (-14.4% 🟢)3.982s (-7.9% 🟢)1.570s81.00x
▲ VercelNext.js (Turbopack)4.906s (+44.4% 🔺)6.610s (+34.0% 🔺)1.704s52.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.228s (-48.0% 🟢)2.008s (-33.3% 🟢)0.780s151.00x
🐘 PostgresNitro1.238s (-47.3% 🟢)2.006s (-33.3% 🟢)0.768s151.01x
💻 LocalExpress1.598s (-45.9% 🟢)2.005s (-41.9% 🟢)0.407s151.30x
💻 LocalNitro1.894s (-39.8% 🟢)2.149s (-44.7% 🟢)0.256s141.54x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.300s (-18.6% 🟢)5.165s (-12.8% 🟢)1.865s61.00x
▲ VercelNext.js (Turbopack)5.590s (-21.3% 🟢)7.240s (-18.7% 🟢)1.651s51.69x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.380s (-60.4% 🟢)2.009s (-49.9% 🟢)0.629s151.00x
🐘 PostgresNitro1.390s (-60.0% 🟢)2.008s (-49.9% 🟢)0.617s151.01x
💻 LocalExpress4.503s (-46.0% 🟢)4.725s (-47.7% 🟢)0.222s73.26x
💻 LocalNitro5.496s (-34.2% 🟢)6.014s (-33.3% 🟢)0.518s53.98x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)5.990s (-32.8% 🟢)7.487s (-31.7% 🟢)1.496s51.00x
▲ VercelNitro6.899s (+95.7% 🔺)10.766s (+94.6% 🔺)3.867s31.15x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.159s (-7.8% 🟢)2.009s (~)0.850s151.00x
🐘 PostgresNitro1.165s (-7.4% 🟢)2.009s (~)0.844s151.00x
💻 LocalExpress1.317s (-30.5% 🟢)2.005s (-15.2% 🟢)0.689s151.14x
💻 LocalNitro1.415s (-24.1% 🟢)2.007s (-14.3% 🟢)0.591s151.22x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.462s (~)3.978s (-4.6%)1.516s81.00x
▲ VercelNext.js (Turbopack)5.008s (+70.8% 🔺)6.602s (+42.2% 🔺)1.594s52.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.221s (-47.8% 🟢)2.009s (-33.3% 🟢)0.788s151.00x
🐘 PostgresExpress1.241s (-47.0% 🟢)2.008s (-33.3% 🟢)0.767s151.02x
💻 LocalExpress1.666s (-46.8% 🟢)2.005s (-46.7% 🟢)0.339s151.36x
💻 LocalNitro2.096s (-31.6% 🟢)2.592s (-33.3% 🟢)0.496s121.72x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.255s (+0.7%)5.219s (+2.8%)1.965s61.00x
▲ VercelNext.js (Turbopack)4.456s (+41.8% 🔺)6.286s (+39.0% 🔺)1.830s51.37x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.358s (-61.0% 🟢)2.007s (-49.9% 🟢)0.649s151.00x
🐘 PostgresExpress1.379s (-60.6% 🟢)2.008s (-49.9% 🟢)0.628s151.02x
💻 LocalExpress4.938s (-43.9% 🟢)5.513s (-40.6% 🟢)0.575s63.64x
💻 LocalNitro6.426s (-29.7% 🟢)6.819s (-32.0% 🟢)0.393s54.73x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.698s (-7.8% 🟢)6.786s (~)2.088s51.00x
▲ VercelNext.js (Turbopack)5.481s (-18.9% 🟢)7.171s (-16.1% 🟢)1.689s51.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.429s (-47.8% 🟢)1.006s (~)0.578s601.00x
🐘 PostgresExpress0.435s (-48.2% 🟢)1.007s (-1.6%)0.572s601.01x
💻 LocalExpress0.444s (-54.8% 🟢)1.004s (-6.7% 🟢)0.559s601.04x
💻 LocalNitro0.471s (-52.0% 🟢)1.004s (-8.2% 🟢)0.534s601.10x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.925s (-77.7% 🟢)7.020s (-70.8% 🟢)2.095s91.00x
▲ VercelNext.js (Turbopack)6.772s (-53.3% 🟢)8.207s (-49.0% 🟢)1.435s81.37x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.028s (-46.7% 🟢)1.413s (-32.7% 🟢)0.384s641.00x
🐘 PostgresExpress1.065s (-46.1% 🟢)1.771s (-21.5% 🟢)0.706s511.04x
💻 LocalExpress1.125s (-62.7% 🟢)2.005s (-44.1% 🟢)0.880s451.09x
💻 LocalNitro1.184s (-61.0% 🟢)2.006s (-46.6% 🟢)0.822s451.15x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro12.382s (-68.6% 🟢)14.665s (-64.5% 🟢)2.283s71.00x
▲ VercelNext.js (Turbopack)15.639s (-68.6% 🟢)17.695s (-65.8% 🟢)2.056s61.26x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.974s (-51.9% 🟢)2.380s (-48.3% 🟢)0.405s511.00x
🐘 PostgresExpress2.076s (-48.0% 🟢)2.529s (-42.1% 🟢)0.453s481.05x
💻 LocalExpress2.545s (-72.4% 🟢)3.032s (-69.7% 🟢)0.488s401.29x
💻 LocalNitro2.664s (-71.4% 🟢)3.057s (-69.5% 🟢)0.394s401.35x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro38.936s (-59.8% 🟢)41.471s (-57.9% 🟢)2.535s41.00x
▲ VercelNext.js (Turbopack)45.359s (-57.7% 🟢)47.056s (-56.8% 🟢)1.697s31.16x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.186s (-34.3% 🟢)1.005s (~)0.819s601.00x
🐘 PostgresExpress0.200s (-29.0% 🟢)1.006s (~)0.806s601.08x
💻 LocalExpress0.389s (-30.6% 🟢)1.004s (~)0.615s602.09x
💻 LocalNitro0.441s (-27.1% 🟢)1.004s (-1.7%)0.563s602.37x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.370s (+42.7% 🔺)3.938s (+17.5% 🔺)1.568s161.00x
▲ VercelNext.js (Turbopack)3.739s (+84.9% 🔺)5.211s (+37.4% 🔺)1.473s121.58x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.307s (-38.1% 🟢)1.006s (~)0.699s901.00x
🐘 PostgresExpress0.329s (-35.5% 🟢)1.006s (~)0.678s901.07x
💻 LocalExpress2.111s (-16.0% 🟢)2.684s (-10.8% 🟢)0.573s346.88x
💻 LocalNitro2.201s (-13.3% 🟢)2.945s (-2.1%)0.744s317.17x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.219s (+123.8% 🔺)9.007s (+86.8% 🔺)1.788s101.00x
▲ VercelNext.js (Turbopack)7.230s (+104.5% 🔺)8.888s (+71.1% 🔺)1.657s111.00x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.623s (-21.2% 🟢)1.006s (~)0.383s1201.00x
🐘 PostgresExpress0.676s (-17.4% 🟢)1.007s (-1.1%)0.330s1201.09x
💻 LocalExpress8.906s (-20.4% 🟢)9.485s (-20.6% 🟢)0.579s1314.30x
💻 LocalNitro10.253s (-8.4% 🟢)10.694s (-8.3% 🟢)0.441s1216.47x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)22.466s (+117.5% 🔺)23.969s (+95.1% 🔺)1.504s61.00x
▲ VercelNitro24.505s (+217.3% 🔺)26.705s (+184.1% 🔺)2.200s51.09x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.120s (+462.6% 🔺)2.004s (+99.5% 🔺)0.009s (-22.3% 🟢)2.016s (+98.0% 🔺)0.896s101.00x
💻 LocalNitro1.134s (+430.7% 🔺)2.006s (+99.7% 🔺)0.012s (-0.8%)2.020s (+98.3% 🔺)0.886s101.01x
🐘 PostgresNitro1.140s (+456.2% 🔺)2.002s (+100.3% 🔺)0.001s (-13.3% 🟢)2.011s (+98.8% 🔺)0.871s101.02x
🐘 PostgresExpress1.159s (+465.2% 🔺)1.998s (+100.1% 🔺)0.001s (-12.5% 🟢)2.011s (+98.8% 🔺)0.852s101.03x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.439s (-36.4% 🟢)3.535s (-33.0% 🟢)2.339s (+215.2% 🔺)6.477s (~)4.038s101.00x
▲ VercelNext.js (Turbopack)4.213s (-38.5% 🟢)4.295s (-50.3% 🟢)1.310s (+107.3% 🔺)7.294s (-25.5% 🟢)3.081s101.73x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.488s (+138.4% 🔺)2.003s (+99.0% 🔺)0.004s (-3.3%)2.025s (+98.0% 🔺)0.536s301.00x
💻 LocalNitro1.523s (+81.6% 🔺)2.012s (+98.8% 🔺)0.010s (+5.3% 🔺)2.023s (+81.3% 🔺)0.500s301.02x
🐘 PostgresExpress1.540s (+144.4% 🔺)2.009s (+99.6% 🔺)0.004s (+3.6%)2.026s (+98.0% 🔺)0.486s301.03x
💻 LocalExpress1.665s (+119.9% 🔺)2.009s (+95.2% 🔺)0.009s (-1.0%)2.199s (+111.5% 🔺)0.534s281.12x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.887s (-80.0% 🟢)7.416s (-75.9% 🟢)0.184s (+64.0% 🔺)8.100s (-74.5% 🟢)2.213s81.00x
▲ VercelNext.js (Turbopack)13.623s (-19.5% 🟢)13.512s (-25.9% 🟢)0.199s (-5.7% 🟢)15.368s (-18.8% 🟢)1.744s42.31x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.653s (-32.6% 🟢)1.050s (-15.9% 🟢)0.000s (-57.9% 🟢)1.060s (-15.7% 🟢)0.407s571.00x
🐘 PostgresExpress0.672s (-30.0% 🟢)1.033s (-19.2% 🟢)0.000s (+19.0% 🔺)1.042s (-20.2% 🟢)0.370s581.03x
💻 LocalExpress1.274s (+4.0%)2.013s (~)0.000s (-40.0% 🟢)2.015s (~)0.740s301.95x
💻 LocalNitro1.310s (+7.1% 🔺)2.017s (~)0.001s (+400.0% 🔺)2.019s (~)0.709s302.01x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.313s (+8.6% 🔺)4.592s (+4.5%)0.000s (+8.3% 🔺)5.023s (+4.5%)1.710s121.00x
▲ VercelNext.js (Turbopack)5.512s (-45.9% 🟢)6.062s (-47.4% 🟢)0.000s (+Infinity% 🔺)6.982s (-42.1% 🟢)1.470s91.66x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.168s (-34.8% 🟢)1.996s (-6.8% 🟢)0.000s (-100.0% 🟢)2.026s (-6.8% 🟢)0.858s301.00x
🐘 PostgresExpress1.377s (-22.3% 🟢)2.066s (-5.1% 🟢)0.000s (NaN%)2.082s (-5.3% 🟢)0.705s291.18x
💻 LocalExpress2.968s (-14.4% 🟢)3.555s (-11.9% 🟢)0.000s (-70.6% 🟢)3.559s (-11.8% 🟢)0.591s172.54x
💻 LocalNitro3.084s (-9.0% 🟢)3.781s (-6.2% 🟢)0.000s (-29.7% 🟢)3.783s (-6.3% 🟢)0.700s162.64x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.124s (+25.2% 🔺)6.356s (+18.3% 🔺)0.000s (-100.0% 🟢)6.813s (+17.6% 🔺)1.689s91.00x
▲ VercelNext.js (Turbopack)7.535s (+34.2% 🔺)8.909s (+27.6% 🔺)0.000s (-100.0% 🟢)9.344s (+23.9% 🔺)1.809s71.47x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress18/21
🐘 PostgresNitro17/21
▲ VercelNitro19/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

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

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actionsBot commented May 4, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production92502191144
✅ 💻 Local Development123702191456
✅ 📦 Local Production123702191456
✅ 🐘 Local Postgres123702191456
✅ 🪟 Windows10400104
✅ 📋 Other5520176728
Total5292010526344

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro78026
✅ example78026
✅ express78026
✅ fastify78026
✅ hono78026
✅ nextjs-turbopack10202
✅ nextjs-webpack10202
✅ nitro78026
✅ nuxt78026
✅ sveltekit9707
✅ vite78026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack10400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable79025
✅ e2e-local-dev-tanstack-start-79025
✅ e2e-local-postgres-nest-stable79025
✅ e2e-local-postgres-tanstack-start-79025
✅ e2e-local-prod-nest-stable79025
✅ e2e-local-prod-tanstack-start-79025
✅ e2e-vercel-prod-tanstack-start78026

📋 View full workflow run

Adds allowedDevOrigins entries so portless-style worktree-prefixed
.localhost URLs (e.g. https://<branch>.turbopack.localhost) can hit
HMR and dev-only endpoints without Next's cross-origin protection
flooding the logs with warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

CopilotAI 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.

Pull request overview

Adds new end-to-end coverage for Promise.race behavior between a long-running step and sleep(), and introduces a new dev-tmux skill to standardize a local 3-pane dev setup (workbench + observability UI + scratchpad) using portless for worktree-isolated .localhost URLs.

Changes:

  • Add sleepWinsRaceWorkflow / stepWinsRaceWorkflow workflows to the workbench e2e workflow collection.
  • Add corresponding Vitest e2e tests asserting the expected race winner and duration bounds.
  • Add skills/dev-tmux/SKILL.md documentation for a tmux + portless local dev workflow; add a (currently empty) changeset file.

Reviewed changes

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

FileDescription
workbench/example/workflows/99_e2e.tsAdds two new workflows plus a shared step helper to exercise Promise.race between a step and sleep().
packages/core/e2e/e2e.test.tsAdds two e2e tests covering the new race workflows and asserting winner + duration.
skills/dev-tmux/SKILL.mdNew skill documenting a portless-routed 3-pane tmux setup for local SDK dev + e2e runs.
.changeset/better-pets-reply.mdAdds a new changeset file, but it is currently empty/invalid.

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

Comment on lines +584 to +590
test('sleepWinsRaceWorkflow', { timeout: 60_000 }, async () => {
const run = await start(await e2e('sleepWinsRaceWorkflow'), []);
const returnValue = await run.returnValue;
expect(returnValue.winner).toBe('sleep');
// Sleep is 1s; step would take 10s. Should resolve in ~1s, well under 5s.
expect(returnValue.durationMs).toBeLessThan(5_000);
});

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Obsolete after rebase onto main — PR #1924 fixed the underlying race semantics and added these same tests directly to main. The duplicate set on this branch was dropped (031e4b8); the race tests are no longer part of this PR's diff.

Comment thread.changeset/better-pets-reply.md
Comment threadskills/dev-tmux/SKILL.md Outdated
Comment on lines +51 to +59
tmux send-keys -t "$SESSION".1 \
'cd workbench/nextjs-turbopack && WORKFLOW_PUBLIC_MANIFEST=1 portless run --name turbopack pnpm dev' C-m

# Pane 2 (top-right): observability UI scoped to the workbench app
tmux send-keys -t "$SESSION".2 \
'cd workbench/nextjs-turbopack && portless run --name workflow-obs sh -c "pnpm workflow web --webPort \$PORT --noBrowser"' C-m

# Pane 3 (bottom-right): scratchpad at repo root
tmux send-keys -t "$SESSION".3 'echo "scratchpad: $(pwd)"' C-m

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Fixed in 5a44360. The skill now captures each pane's ID at split time with -P -F '#{pane_id}' and uses those IDs as targets, so it's correct under both pane-base-index 0 and pane-base-index 1 regardless of the user's tmux config.

Comment threadpackages/core/e2e/e2e.test.ts
TooTallNate
TooTallNate previously requested changes May 4, 2026

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verdict: Request Changes

The PR has two things in it: e2e race tests (good idea, surfacing a real V2-handler semantics question) and a personal-dev skill file. I have one blocker on the test, one scope concern on the skill, and one minor nit on the next.config change.


Summary of inline comments

  1. packages/core/e2e/e2e.test.ts:587 — Blocker.sleepWinsRaceWorkflow fails on every e2e job (56+ jobs across all frameworks × all environments). Needs test.fails(...), .skip with TODO, or removal until the underlying race semantics are decided. The PR description correctly identifies this as intentional pending discussion, but the current state makes the e2e matrix unusable for any PR based on this one.

  2. skills/dev-tmux/SKILL.md:3 — Scope. Activation phrases ("spin up the dev session", "start dev mode", "set up the workbench") will trigger for general contributor questions, then push them toward installing portless. Either narrow the phrases to portless-specific terms or move the skill to a user-level location.

  3. workbench/nextjs-turbopack/next.config.ts:13 — Nit. Hardcoding *.turbopack.localhost in the workbench bakes one developer's convention into the shared config. Dev-only, so harmless — flagging for awareness, not blocking.


What I verified

  • Diagnosis is correct: The V2 inline-execution model in workflowEntrypoint (packages/core/src/runtime.ts) executes step bodies inline rather than enqueueing them. A 10-second delayMsStep blocks the handler for 10 seconds, which prevents the 1-second sleep('1s') timer from firing on time. So Promise.race ends up resolving with 'step' because that's what the handler delivers first. Whether this should be considered correct behavior or a bug is a real question.
  • Symlinks: Confirmed 99_e2e.ts is symlinked from workbench/example/workflows/99_e2e.ts into all other workbenches per AGENTS.md convention. ✅
  • Empty changeset: ---\n--- is the right pattern for tests-only PRs that don't ship package changes; matches existing examples (fix-vite-step-source-maps-e2e.md, tanstack-start-workbench.md). ✅
  • Skill frontmatter: version: '1.0' is fine for a new skill (existing migrating-to-workflow-sdk is '0.2.0'). Format matches.
  • Typecheck: Clean across all 40 packages.

Suggested path forward

Smallest change that keeps the value of this PR:

  1. Mark sleepWinsRaceWorkflow with test.fails(...) and add a code comment linking to an issue tracking the V2 race-semantics decision. That preserves the test as a regression detector — if the underlying behavior is ever fixed, the test will go red and force a follow-up.
  2. Optional: open a separate small skill PR after clarifying scope, or move the skill to a personal location.
  3. Land stepWinsRaceWorkflow as a regular passing test — it's a useful regression check on the easy direction.

Happy to re-review once the failing assertion is addressed.

test('sleepWinsRaceWorkflow', { timeout: 60_000 }, async () => {
const run = await start(await e2e('sleepWinsRaceWorkflow'), []);
const returnValue = await run.returnValue;
expect(returnValue.winner).toBe('sleep');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocker — every e2e CI job is failing because of this assertion.

This test fails on every framework's e2e run (express, hono, nitro, fastify, astro, vite, nuxt, sveltekit, tanstack-start, both Next.js variants — local-dev, local-prod, local-postgres, AND vercel-prod) because 99_e2e.ts is shared across all workbenches via symlink. As of the latest run, that's 56+ failed jobs all on expected 'step' to be 'sleep'.

For example: E2E Local Dev Tests (express - stable)FAIL packages/core/e2e/e2e.test.ts > e2e > sleepWinsRaceWorkflow / AssertionError: expected 'step' to be 'sleep'.

The PR description acknowledges this is intentional and wants discussion before merge. That's reasonable as a discussion vehicle, but it can't merge in this state — the PR effectively makes the entire e2e matrix red for any other PR that bases on this one.

Until the underlying behavior question is settled, please pick one:

  1. test.fails(...) — vitest will pass when the assertion fails, and flip to red if/when the runtime is fixed. Cleanly documents the known-broken behavior.
  2. test.skip(...) with a TODO comment linking to the discussion / issue tracking the race semantics decision.
  3. Drop the failing test from this PR and land just stepWinsRaceWorkflow (which is the meaningful regression-prevention test); open a follow-up issue or PR for the slow-step-vs-fast-sleep case once the semantics are decided.

For reference, the underlying cause is consistent with the V2 inline-execution model in packages/core/src/runtime.ts (workflowEntrypoint): the handler executes the step inline rather than enqueuing it, so a 10s step blocks the handler for 10s and prevents the 1s sleep timer from firing on time. That's a real semantics question worth resolving — but in a separate change.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Resolved — merged latest main, which carried PR #1924's fix for the inline-execution behavior and added these same race tests directly. The duplicate set landed by this branch was dropped (commit 031e4b8), so 99_e2e.ts now matches main exactly and the race tests are no longer part of this PR's diff. Both sleepWinsRaceWorkflow and stepWinsRaceWorkflow now run from main's definitions and pass. This PR is dev-tooling only after the rebase.

Comment threadskills/dev-tmux/SKILL.md Outdated
@@ -0,0 +1,125 @@
---
name: dev-tmux
description: Spin up a 3-pane tmux session for local Workflow SDK development — Next.js turbopack workbench, observability UI, and a scratchpad — all routed through portless so each worktree gets isolated `.localhost` URLs. Use when the user asks to "spin up the dev session", "start dev mode", "set up the workbench", or any time they want to test workflows interactively in a worktree alongside the observability UI.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Activation phrases here are very generic — "spin up the dev session", "start dev mode", "set up the workbench" all describe the general task most contributors do daily, not the specific portless+tmux setup this skill describes. As written, this skill will activate for any contributor asking how to start the dev server, then drive them toward installing portless and adopting a personal tmux layout that the rest of the team doesn't use.

A couple of options:

  1. Narrow the activation phrases to portless-specific terms: "portless dev session", "tmux dev layout", "worktree-isolated dev URLs", etc. That way the skill only fires for users who already know they want this specific tooling.
  2. Move it out of the shared skills/ directory. This appears to be one developer's personal workflow tooling rather than a project-wide convention — skills/migrating-to-workflow-sdk/ is documenting a user-facing capability, whereas this is documenting an internal contributor's local-dev preference. A user-level agent skill (e.g. ~/.agents/skills/dev-tmux/) would keep it personal without affecting other contributors.

(The skill itself is well-written and the troubleshooting section is genuinely useful — this is purely about scope/activation, not quality.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Tightened in 5a44360 (v1.1 of the skill). The description now opens with Spin up a portless + tmux dev session ... and explicitly lists the only triggers as portless dev session / tmux dev layout for workflow / worktree-isolated dev URLs / wanting to wire URLs into the Claude statusline. It also explicitly says not to activate for the generic start the dev server / run pnpm dev task. Should now stay out of any contributor's path who isn't already opting into this tooling.

Kept it under skills/ rather than moving to ~/.claude/ because v1.1 also adds a statusline.sh helper script that needs to live next to the skill — moving it out would split the two artifacts. Open to revisiting if option 2 still feels right.

Comment threadworkbench/nextjs-turbopack/next.config.ts
Worktrees get deleted, so wiring the statusline to a worktree path
breaks the moment the worktree is removed. Update the skill and the
script header to recommend pointing `statusLine.command` at the
primary checkout (`$HOME/github/vercel/workflow/...`). The script
itself is already worktree-aware via Claude's `workspace.current_dir`
stdin JSON, so the same invocation surfaces routes for whichever
worktree the session is in.
Bump version to 1.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment threadskills/dev-tmux/SKILL.md Outdated
Comment on lines +5 to +6
author: Vercel Inc.
version: '1.2'

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

change the author to "Pranay Prakash" and set the version to 0.1 for this first release (since it's a new skill anyway)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 8b7225bmetadata.author: Pranay Prakash, metadata.version: '0.1'.

Comment threadskills/dev-tmux/SKILL.md Outdated
@@ -0,0 +1,160 @@
---
name: dev-tmux

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Suggested change
name: dev-tmux
name: internal-dev-workbench

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Applied in 8b7225bgit mv skills/dev-tmux skills/internal-dev-workbench, plus the matching frontmatter name: change and updates to all internal references in SKILL.md and statusline.sh.

@pranaygp

Copy link
Copy Markdown
ContributorAuthor

making some changes to the skill and testing it a bit more. moving to draft for now

pranaygpand others added 4 commits May 5, 2026 10:50
- Statusline overlay now renders `[dev] · [obs] · tmux:<prefix>`,
with the bracketed labels emitted as OSC 8 hyperlinks (clickable in
any modern terminal) styled cyan + underline so they stand out.
Replaces the old long-URL form that was hard to scan and click.
- Add a tmux-session indicator: shown when a session named exactly the
worktree prefix exists (uses `tmux has-session -t =<prefix>` for
exact matching).
- Change the skill's tmux session naming convention from the fixed
`workflow-dev` to `<worktree-prefix>` (basename of the branch — same
string portless uses as the subdomain prefix). This lets the
statusline locate the session deterministically and lets multiple
worktrees run dev sessions concurrently without manual disambiguation.
- Bump skill to v1.3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the abbreviated `tmux:<prefix>` indicator with the full
copy-paste-ready `tmux attach -t <prefix>` invocation. Saves a step
when grabbing the session from another shell.
Bump skill to v1.4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop the dim styling that made the overlay hard to read; use bold
bright cyan + underline for links and bold bright green for the
tmux command.
- Add Nerd Font glyphs: for dev, for obs, for the
tmux copy-paste hint. Falls back to box-drawing if the font lacks
Nerd Font ranges; layout is unaffected.
- Visual differentiation: cyan + underline = clickable hyperlink;
green = copy this command.
Bump skill to v1.5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The copy glyph in `emit_tmux` was a literal Nerd Font byte embedded in
the printf string and got stripped during a prior rewrite. Promote all
three icons (rocket / graph / copy) to top-level shell variables that
use \uHHHH-equivalent UTF-8 escapes, so the source survives editor
round-trips that don't preserve Private Use Area code points.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pranaygp

Copy link
Copy Markdown
ContributorAuthor

Updates since the previous round of review replies — all in skills/dev-tmux/:

Statusline overhaul (commits c714162938c26e)

  • The statusline command should be wired against the primary checkout ($HOME/github/vercel/workflow/skills/dev-tmux/statusline.sh), not a worktree path that disappears on cleanup. The script remains worktree-aware via Claude's workspace.current_dir stdin JSON, so a single stable path correctly surfaces routes for whichever worktree the session is in.
  • Output format reworked from long URLs to compact OSC 8 hyperlinks: [rocket] dev · [graph] obs · [copy] tmux attach -t <prefix>. The bracketed parts are clickable in iTerm2 / Kitty / WezTerm / Terminal.app / Ghostty (cmd/ctrl-click).
  • Bold + bright styling — bold bright cyan + underline for the clickable links, bold bright green for the tmux attach command (visually distinguishes "click this" from "copy this"). Earlier dim styling was hard to read.
  • Nerd Font icons (octicon rocket, octicon graph, fa copy) defined as top-level shell variables using UTF-8 escapes so they survive editor round-trips that don't preserve PUA code points.

Tmux session naming convention (commit 431026c)

  • The skill now creates the session with <worktree-prefix> (basename of the branch — same string portless uses for the <prefix>.<name>.localhost subdomain) instead of the fixed workflow-dev. Lets multiple worktrees run dev sessions concurrently with no manual disambiguation, and lets the statusline locate the session deterministically via tmux has-session -t =<prefix>.
  • The statusline shows the full tmux attach -t <prefix> invocation (commit b0f373c) so it's directly copy-pasteable into another shell.

Skill version bumped to 1.5. Marking ready for review.

…set version
- Rename `skills/dev-tmux/` → `skills/internal-dev-workbench/` to make
the name self-explanatory about the skill's scope (an internal
contributor's local dev workbench, not a generic tmux helper).
- Author: Pranay Prakash. Version: 0.1 (first release of the skill).
- Update internal references in SKILL.md and statusline.sh accordingly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

4 participants

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

Add dev-tmux skill for portless+tmux local Workflow SDK dev - #1916

Merged
pranaygp merged 11 commits into
mainfrom
pgp/stepflow-test
May 5, 2026
Merged

Add dev-tmux skill for portless+tmux local Workflow SDK dev#1916
pranaygp merged 11 commits into
mainfrom
pgp/stepflow-test

Conversation

@pranaygp

@pranaygppranaygp commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • New dev-tmux skill documenting the 3-pane tmux + portless setup for local Workflow SDK development (turbopack workbench, observability UI scoped to that workbench, scratchpad shell — all worktree-isolated via portless's branch-prefixed .localhost URLs).
  • Adds allowedDevOrigins: ['turbopack.localhost', '*.turbopack.localhost'] to workbench/nextjs-turbopack/next.config.ts so portless-style URLs don't get blocked by Next's cross-origin protection in dev (currently floods the logs with HMR warnings).

What changed since the original PR

The original PR also added two race workflows (sleepWinsRaceWorkflow, stepWinsRaceWorkflow) and their e2e tests to surface a Promise.race-vs-replay semantics issue. Those landed independently in #1924 (with the underlying fix), so this branch was rebased onto main and the duplicate additions removed — the race tests now come from main. This PR is dev-tooling only.

Test plan

  • pnpm vitest run packages/core/e2e/e2e.test.ts -t WinsRaceWorkflow against the turbopack workbench (passes after rebase, since [core] V2: skip inline step execution when suspension also has a wait #1924's fix is on main)
  • Spin up the workbench by following skills/dev-tmux/SKILL.md and confirm both https://<branch>.turbopack.localhost (HMR works, no cross-origin warnings) and https://<branch>.workflow-obs.localhost (observability UI shows runs from the workbench)

🤖 Generated with Claude Code

Adds two race workflows (sleepWinsRaceWorkflow, stepWinsRaceWorkflow)
that exercise Promise.race between a step function and a sleep call.
The current `sleepWinsRaceWorkflow` test fails — surfacing how the
replay engine resolves a previously-completed step instantly while
sleep still has to elapse.
Also adds a `dev-tmux` skill that documents the 3-pane tmux + portless
setup for testing workflows interactively in a worktree alongside the
observability UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings May 4, 2026 11:48
@changeset-bot

changeset-botBot commented May 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8b7225b

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 May 4, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 4, 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🥇 Nitro0.031s (-27.8% 🟢)1.005s (~)0.974s101.00x
💻 LocalExpress0.034s (-22.3% 🟢)1.005s (~)0.970s101.11x
🐘 PostgresExpress0.046s (-20.7% 🟢)1.012s (~)0.966s101.48x
🐘 PostgresNitro0.046s (-51.7% 🟢)1.012s (-3.0%)0.966s101.48x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.315s (-23.1% 🟢)2.072s (-17.4% 🟢)1.758s101.00x
▲ VercelNext.js (Turbopack)0.793s (+215.3% 🔺)2.620s (+12.3% 🔺)1.827s102.52x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.068s (-5.1% 🟢)2.006s (~)0.938s101.00x
💻 LocalNitro1.073s (-5.2% 🟢)2.006s (~)0.933s101.00x
🐘 PostgresNitro1.083s (-5.0%)2.011s (~)0.927s101.01x
🐘 PostgresExpress1.085s (-5.4% 🟢)2.010s (~)0.925s101.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.777s (-54.3% 🟢)3.967s (-32.9% 🟢)2.190s101.00x
▲ VercelNext.js (Turbopack)2.267s (+11.4% 🔺)3.754s (-2.0%)1.487s101.28x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.402s (-4.8%)11.020s (~)0.618s31.00x
🐘 PostgresNitro10.402s (-4.3%)11.017s (~)0.615s31.00x
💻 LocalNitro10.414s (-4.9%)11.022s (~)0.607s31.00x
🐘 PostgresExpress10.446s (-4.7%)11.019s (~)0.574s31.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.463s (-43.3% 🟢)15.032s (-40.2% 🟢)1.568s21.00x
▲ VercelNext.js (Turbopack)14.098s (-18.6% 🟢)15.843s (-18.3% 🟢)1.745s21.05x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.436s (-10.2% 🟢)14.025s (-6.7% 🟢)0.589s51.00x
🐘 PostgresNitro13.445s (-7.9% 🟢)14.018s (-6.7% 🟢)0.573s51.00x
💻 LocalNitro13.481s (-10.5% 🟢)14.027s (-12.5% 🟢)0.546s51.00x
🐘 PostgresExpress13.493s (-7.5% 🟢)14.024s (-6.6% 🟢)0.531s51.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro21.085s (-67.3% 🟢)23.044s (-65.4% 🟢)1.959s31.00x
▲ VercelNext.js (Turbopack)22.401s (-57.4% 🟢)24.203s (-55.7% 🟢)1.802s31.06x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro11.880s (-14.9% 🟢)12.016s (-16.0% 🟢)0.136s81.00x
💻 LocalNitro11.939s (-28.9% 🟢)12.147s (-28.7% 🟢)0.208s81.00x
💻 LocalExpress11.974s (-27.9% 🟢)12.396s (-27.2% 🟢)0.422s81.01x
🐘 PostgresExpress12.130s (-13.4% 🟢)12.642s (-13.4% 🟢)0.512s81.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro30.301s (-92.8% 🟢)32.393s (-92.4% 🟢)2.092s31.00x
▲ VercelNext.js (Turbopack)34.399s (-91.3% 🟢)36.050s (-90.9% 🟢)1.651s31.14x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.156s (-22.3% 🟢)2.005s (~)0.849s151.00x
🐘 PostgresNitro1.159s (-9.1% 🟢)2.008s (~)0.849s151.00x
🐘 PostgresExpress1.160s (-8.0% 🟢)2.008s (~)0.848s151.00x
💻 LocalNitro1.180s (-27.6% 🟢)2.006s (-3.3%)0.825s151.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.412s (-14.4% 🟢)3.982s (-7.9% 🟢)1.570s81.00x
▲ VercelNext.js (Turbopack)4.906s (+44.4% 🔺)6.610s (+34.0% 🔺)1.704s52.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.228s (-48.0% 🟢)2.008s (-33.3% 🟢)0.780s151.00x
🐘 PostgresNitro1.238s (-47.3% 🟢)2.006s (-33.3% 🟢)0.768s151.01x
💻 LocalExpress1.598s (-45.9% 🟢)2.005s (-41.9% 🟢)0.407s151.30x
💻 LocalNitro1.894s (-39.8% 🟢)2.149s (-44.7% 🟢)0.256s141.54x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.300s (-18.6% 🟢)5.165s (-12.8% 🟢)1.865s61.00x
▲ VercelNext.js (Turbopack)5.590s (-21.3% 🟢)7.240s (-18.7% 🟢)1.651s51.69x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.380s (-60.4% 🟢)2.009s (-49.9% 🟢)0.629s151.00x
🐘 PostgresNitro1.390s (-60.0% 🟢)2.008s (-49.9% 🟢)0.617s151.01x
💻 LocalExpress4.503s (-46.0% 🟢)4.725s (-47.7% 🟢)0.222s73.26x
💻 LocalNitro5.496s (-34.2% 🟢)6.014s (-33.3% 🟢)0.518s53.98x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)5.990s (-32.8% 🟢)7.487s (-31.7% 🟢)1.496s51.00x
▲ VercelNitro6.899s (+95.7% 🔺)10.766s (+94.6% 🔺)3.867s31.15x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.159s (-7.8% 🟢)2.009s (~)0.850s151.00x
🐘 PostgresNitro1.165s (-7.4% 🟢)2.009s (~)0.844s151.00x
💻 LocalExpress1.317s (-30.5% 🟢)2.005s (-15.2% 🟢)0.689s151.14x
💻 LocalNitro1.415s (-24.1% 🟢)2.007s (-14.3% 🟢)0.591s151.22x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.462s (~)3.978s (-4.6%)1.516s81.00x
▲ VercelNext.js (Turbopack)5.008s (+70.8% 🔺)6.602s (+42.2% 🔺)1.594s52.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.221s (-47.8% 🟢)2.009s (-33.3% 🟢)0.788s151.00x
🐘 PostgresExpress1.241s (-47.0% 🟢)2.008s (-33.3% 🟢)0.767s151.02x
💻 LocalExpress1.666s (-46.8% 🟢)2.005s (-46.7% 🟢)0.339s151.36x
💻 LocalNitro2.096s (-31.6% 🟢)2.592s (-33.3% 🟢)0.496s121.72x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.255s (+0.7%)5.219s (+2.8%)1.965s61.00x
▲ VercelNext.js (Turbopack)4.456s (+41.8% 🔺)6.286s (+39.0% 🔺)1.830s51.37x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.358s (-61.0% 🟢)2.007s (-49.9% 🟢)0.649s151.00x
🐘 PostgresExpress1.379s (-60.6% 🟢)2.008s (-49.9% 🟢)0.628s151.02x
💻 LocalExpress4.938s (-43.9% 🟢)5.513s (-40.6% 🟢)0.575s63.64x
💻 LocalNitro6.426s (-29.7% 🟢)6.819s (-32.0% 🟢)0.393s54.73x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.698s (-7.8% 🟢)6.786s (~)2.088s51.00x
▲ VercelNext.js (Turbopack)5.481s (-18.9% 🟢)7.171s (-16.1% 🟢)1.689s51.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.429s (-47.8% 🟢)1.006s (~)0.578s601.00x
🐘 PostgresExpress0.435s (-48.2% 🟢)1.007s (-1.6%)0.572s601.01x
💻 LocalExpress0.444s (-54.8% 🟢)1.004s (-6.7% 🟢)0.559s601.04x
💻 LocalNitro0.471s (-52.0% 🟢)1.004s (-8.2% 🟢)0.534s601.10x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.925s (-77.7% 🟢)7.020s (-70.8% 🟢)2.095s91.00x
▲ VercelNext.js (Turbopack)6.772s (-53.3% 🟢)8.207s (-49.0% 🟢)1.435s81.37x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.028s (-46.7% 🟢)1.413s (-32.7% 🟢)0.384s641.00x
🐘 PostgresExpress1.065s (-46.1% 🟢)1.771s (-21.5% 🟢)0.706s511.04x
💻 LocalExpress1.125s (-62.7% 🟢)2.005s (-44.1% 🟢)0.880s451.09x
💻 LocalNitro1.184s (-61.0% 🟢)2.006s (-46.6% 🟢)0.822s451.15x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro12.382s (-68.6% 🟢)14.665s (-64.5% 🟢)2.283s71.00x
▲ VercelNext.js (Turbopack)15.639s (-68.6% 🟢)17.695s (-65.8% 🟢)2.056s61.26x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.974s (-51.9% 🟢)2.380s (-48.3% 🟢)0.405s511.00x
🐘 PostgresExpress2.076s (-48.0% 🟢)2.529s (-42.1% 🟢)0.453s481.05x
💻 LocalExpress2.545s (-72.4% 🟢)3.032s (-69.7% 🟢)0.488s401.29x
💻 LocalNitro2.664s (-71.4% 🟢)3.057s (-69.5% 🟢)0.394s401.35x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro38.936s (-59.8% 🟢)41.471s (-57.9% 🟢)2.535s41.00x
▲ VercelNext.js (Turbopack)45.359s (-57.7% 🟢)47.056s (-56.8% 🟢)1.697s31.16x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.186s (-34.3% 🟢)1.005s (~)0.819s601.00x
🐘 PostgresExpress0.200s (-29.0% 🟢)1.006s (~)0.806s601.08x
💻 LocalExpress0.389s (-30.6% 🟢)1.004s (~)0.615s602.09x
💻 LocalNitro0.441s (-27.1% 🟢)1.004s (-1.7%)0.563s602.37x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.370s (+42.7% 🔺)3.938s (+17.5% 🔺)1.568s161.00x
▲ VercelNext.js (Turbopack)3.739s (+84.9% 🔺)5.211s (+37.4% 🔺)1.473s121.58x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.307s (-38.1% 🟢)1.006s (~)0.699s901.00x
🐘 PostgresExpress0.329s (-35.5% 🟢)1.006s (~)0.678s901.07x
💻 LocalExpress2.111s (-16.0% 🟢)2.684s (-10.8% 🟢)0.573s346.88x
💻 LocalNitro2.201s (-13.3% 🟢)2.945s (-2.1%)0.744s317.17x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.219s (+123.8% 🔺)9.007s (+86.8% 🔺)1.788s101.00x
▲ VercelNext.js (Turbopack)7.230s (+104.5% 🔺)8.888s (+71.1% 🔺)1.657s111.00x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.623s (-21.2% 🟢)1.006s (~)0.383s1201.00x
🐘 PostgresExpress0.676s (-17.4% 🟢)1.007s (-1.1%)0.330s1201.09x
💻 LocalExpress8.906s (-20.4% 🟢)9.485s (-20.6% 🟢)0.579s1314.30x
💻 LocalNitro10.253s (-8.4% 🟢)10.694s (-8.3% 🟢)0.441s1216.47x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)22.466s (+117.5% 🔺)23.969s (+95.1% 🔺)1.504s61.00x
▲ VercelNitro24.505s (+217.3% 🔺)26.705s (+184.1% 🔺)2.200s51.09x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.120s (+462.6% 🔺)2.004s (+99.5% 🔺)0.009s (-22.3% 🟢)2.016s (+98.0% 🔺)0.896s101.00x
💻 LocalNitro1.134s (+430.7% 🔺)2.006s (+99.7% 🔺)0.012s (-0.8%)2.020s (+98.3% 🔺)0.886s101.01x
🐘 PostgresNitro1.140s (+456.2% 🔺)2.002s (+100.3% 🔺)0.001s (-13.3% 🟢)2.011s (+98.8% 🔺)0.871s101.02x
🐘 PostgresExpress1.159s (+465.2% 🔺)1.998s (+100.1% 🔺)0.001s (-12.5% 🟢)2.011s (+98.8% 🔺)0.852s101.03x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.439s (-36.4% 🟢)3.535s (-33.0% 🟢)2.339s (+215.2% 🔺)6.477s (~)4.038s101.00x
▲ VercelNext.js (Turbopack)4.213s (-38.5% 🟢)4.295s (-50.3% 🟢)1.310s (+107.3% 🔺)7.294s (-25.5% 🟢)3.081s101.73x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.488s (+138.4% 🔺)2.003s (+99.0% 🔺)0.004s (-3.3%)2.025s (+98.0% 🔺)0.536s301.00x
💻 LocalNitro1.523s (+81.6% 🔺)2.012s (+98.8% 🔺)0.010s (+5.3% 🔺)2.023s (+81.3% 🔺)0.500s301.02x
🐘 PostgresExpress1.540s (+144.4% 🔺)2.009s (+99.6% 🔺)0.004s (+3.6%)2.026s (+98.0% 🔺)0.486s301.03x
💻 LocalExpress1.665s (+119.9% 🔺)2.009s (+95.2% 🔺)0.009s (-1.0%)2.199s (+111.5% 🔺)0.534s281.12x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.887s (-80.0% 🟢)7.416s (-75.9% 🟢)0.184s (+64.0% 🔺)8.100s (-74.5% 🟢)2.213s81.00x
▲ VercelNext.js (Turbopack)13.623s (-19.5% 🟢)13.512s (-25.9% 🟢)0.199s (-5.7% 🟢)15.368s (-18.8% 🟢)1.744s42.31x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.653s (-32.6% 🟢)1.050s (-15.9% 🟢)0.000s (-57.9% 🟢)1.060s (-15.7% 🟢)0.407s571.00x
🐘 PostgresExpress0.672s (-30.0% 🟢)1.033s (-19.2% 🟢)0.000s (+19.0% 🔺)1.042s (-20.2% 🟢)0.370s581.03x
💻 LocalExpress1.274s (+4.0%)2.013s (~)0.000s (-40.0% 🟢)2.015s (~)0.740s301.95x
💻 LocalNitro1.310s (+7.1% 🔺)2.017s (~)0.001s (+400.0% 🔺)2.019s (~)0.709s302.01x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.313s (+8.6% 🔺)4.592s (+4.5%)0.000s (+8.3% 🔺)5.023s (+4.5%)1.710s121.00x
▲ VercelNext.js (Turbopack)5.512s (-45.9% 🟢)6.062s (-47.4% 🟢)0.000s (+Infinity% 🔺)6.982s (-42.1% 🟢)1.470s91.66x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.168s (-34.8% 🟢)1.996s (-6.8% 🟢)0.000s (-100.0% 🟢)2.026s (-6.8% 🟢)0.858s301.00x
🐘 PostgresExpress1.377s (-22.3% 🟢)2.066s (-5.1% 🟢)0.000s (NaN%)2.082s (-5.3% 🟢)0.705s291.18x
💻 LocalExpress2.968s (-14.4% 🟢)3.555s (-11.9% 🟢)0.000s (-70.6% 🟢)3.559s (-11.8% 🟢)0.591s172.54x
💻 LocalNitro3.084s (-9.0% 🟢)3.781s (-6.2% 🟢)0.000s (-29.7% 🟢)3.783s (-6.3% 🟢)0.700s162.64x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.124s (+25.2% 🔺)6.356s (+18.3% 🔺)0.000s (-100.0% 🟢)6.813s (+17.6% 🔺)1.689s91.00x
▲ VercelNext.js (Turbopack)7.535s (+34.2% 🔺)8.909s (+27.6% 🔺)0.000s (-100.0% 🟢)9.344s (+23.9% 🔺)1.809s71.47x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress18/21
🐘 PostgresNitro17/21
▲ VercelNitro19/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

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

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actionsBot commented May 4, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production92502191144
✅ 💻 Local Development123702191456
✅ 📦 Local Production123702191456
✅ 🐘 Local Postgres123702191456
✅ 🪟 Windows10400104
✅ 📋 Other5520176728
Total5292010526344

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro78026
✅ example78026
✅ express78026
✅ fastify78026
✅ hono78026
✅ nextjs-turbopack10202
✅ nextjs-webpack10202
✅ nitro78026
✅ nuxt78026
✅ sveltekit9707
✅ vite78026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack10400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable79025
✅ e2e-local-dev-tanstack-start-79025
✅ e2e-local-postgres-nest-stable79025
✅ e2e-local-postgres-tanstack-start-79025
✅ e2e-local-prod-nest-stable79025
✅ e2e-local-prod-tanstack-start-79025
✅ e2e-vercel-prod-tanstack-start78026

📋 View full workflow run

Adds allowedDevOrigins entries so portless-style worktree-prefixed
.localhost URLs (e.g. https://<branch>.turbopack.localhost) can hit
HMR and dev-only endpoints without Next's cross-origin protection
flooding the logs with warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

CopilotAI 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.

Pull request overview

Adds new end-to-end coverage for Promise.race behavior between a long-running step and sleep(), and introduces a new dev-tmux skill to standardize a local 3-pane dev setup (workbench + observability UI + scratchpad) using portless for worktree-isolated .localhost URLs.

Changes:

  • Add sleepWinsRaceWorkflow / stepWinsRaceWorkflow workflows to the workbench e2e workflow collection.
  • Add corresponding Vitest e2e tests asserting the expected race winner and duration bounds.
  • Add skills/dev-tmux/SKILL.md documentation for a tmux + portless local dev workflow; add a (currently empty) changeset file.

Reviewed changes

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

FileDescription
workbench/example/workflows/99_e2e.tsAdds two new workflows plus a shared step helper to exercise Promise.race between a step and sleep().
packages/core/e2e/e2e.test.tsAdds two e2e tests covering the new race workflows and asserting winner + duration.
skills/dev-tmux/SKILL.mdNew skill documenting a portless-routed 3-pane tmux setup for local SDK dev + e2e runs.
.changeset/better-pets-reply.mdAdds a new changeset file, but it is currently empty/invalid.

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

Comment on lines +584 to +590
test('sleepWinsRaceWorkflow', { timeout: 60_000 }, async () => {
const run = await start(await e2e('sleepWinsRaceWorkflow'), []);
const returnValue = await run.returnValue;
expect(returnValue.winner).toBe('sleep');
// Sleep is 1s; step would take 10s. Should resolve in ~1s, well under 5s.
expect(returnValue.durationMs).toBeLessThan(5_000);
});

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Obsolete after rebase onto main — PR #1924 fixed the underlying race semantics and added these same tests directly to main. The duplicate set on this branch was dropped (031e4b8); the race tests are no longer part of this PR's diff.

Comment thread.changeset/better-pets-reply.md
Comment threadskills/dev-tmux/SKILL.md Outdated
Comment on lines +51 to +59
tmux send-keys -t "$SESSION".1 \
'cd workbench/nextjs-turbopack && WORKFLOW_PUBLIC_MANIFEST=1 portless run --name turbopack pnpm dev' C-m

# Pane 2 (top-right): observability UI scoped to the workbench app
tmux send-keys -t "$SESSION".2 \
'cd workbench/nextjs-turbopack && portless run --name workflow-obs sh -c "pnpm workflow web --webPort \$PORT --noBrowser"' C-m

# Pane 3 (bottom-right): scratchpad at repo root
tmux send-keys -t "$SESSION".3 'echo "scratchpad: $(pwd)"' C-m

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Fixed in 5a44360. The skill now captures each pane's ID at split time with -P -F '#{pane_id}' and uses those IDs as targets, so it's correct under both pane-base-index 0 and pane-base-index 1 regardless of the user's tmux config.

Comment threadpackages/core/e2e/e2e.test.ts
TooTallNate
TooTallNate previously requested changes May 4, 2026

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verdict: Request Changes

The PR has two things in it: e2e race tests (good idea, surfacing a real V2-handler semantics question) and a personal-dev skill file. I have one blocker on the test, one scope concern on the skill, and one minor nit on the next.config change.


Summary of inline comments

  1. packages/core/e2e/e2e.test.ts:587 — Blocker.sleepWinsRaceWorkflow fails on every e2e job (56+ jobs across all frameworks × all environments). Needs test.fails(...), .skip with TODO, or removal until the underlying race semantics are decided. The PR description correctly identifies this as intentional pending discussion, but the current state makes the e2e matrix unusable for any PR based on this one.

  2. skills/dev-tmux/SKILL.md:3 — Scope. Activation phrases ("spin up the dev session", "start dev mode", "set up the workbench") will trigger for general contributor questions, then push them toward installing portless. Either narrow the phrases to portless-specific terms or move the skill to a user-level location.

  3. workbench/nextjs-turbopack/next.config.ts:13 — Nit. Hardcoding *.turbopack.localhost in the workbench bakes one developer's convention into the shared config. Dev-only, so harmless — flagging for awareness, not blocking.


What I verified

  • Diagnosis is correct: The V2 inline-execution model in workflowEntrypoint (packages/core/src/runtime.ts) executes step bodies inline rather than enqueueing them. A 10-second delayMsStep blocks the handler for 10 seconds, which prevents the 1-second sleep('1s') timer from firing on time. So Promise.race ends up resolving with 'step' because that's what the handler delivers first. Whether this should be considered correct behavior or a bug is a real question.
  • Symlinks: Confirmed 99_e2e.ts is symlinked from workbench/example/workflows/99_e2e.ts into all other workbenches per AGENTS.md convention. ✅
  • Empty changeset: ---\n--- is the right pattern for tests-only PRs that don't ship package changes; matches existing examples (fix-vite-step-source-maps-e2e.md, tanstack-start-workbench.md). ✅
  • Skill frontmatter: version: '1.0' is fine for a new skill (existing migrating-to-workflow-sdk is '0.2.0'). Format matches.
  • Typecheck: Clean across all 40 packages.

Suggested path forward

Smallest change that keeps the value of this PR:

  1. Mark sleepWinsRaceWorkflow with test.fails(...) and add a code comment linking to an issue tracking the V2 race-semantics decision. That preserves the test as a regression detector — if the underlying behavior is ever fixed, the test will go red and force a follow-up.
  2. Optional: open a separate small skill PR after clarifying scope, or move the skill to a personal location.
  3. Land stepWinsRaceWorkflow as a regular passing test — it's a useful regression check on the easy direction.

Happy to re-review once the failing assertion is addressed.

test('sleepWinsRaceWorkflow', { timeout: 60_000 }, async () => {
const run = await start(await e2e('sleepWinsRaceWorkflow'), []);
const returnValue = await run.returnValue;
expect(returnValue.winner).toBe('sleep');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocker — every e2e CI job is failing because of this assertion.

This test fails on every framework's e2e run (express, hono, nitro, fastify, astro, vite, nuxt, sveltekit, tanstack-start, both Next.js variants — local-dev, local-prod, local-postgres, AND vercel-prod) because 99_e2e.ts is shared across all workbenches via symlink. As of the latest run, that's 56+ failed jobs all on expected 'step' to be 'sleep'.

For example: E2E Local Dev Tests (express - stable)FAIL packages/core/e2e/e2e.test.ts > e2e > sleepWinsRaceWorkflow / AssertionError: expected 'step' to be 'sleep'.

The PR description acknowledges this is intentional and wants discussion before merge. That's reasonable as a discussion vehicle, but it can't merge in this state — the PR effectively makes the entire e2e matrix red for any other PR that bases on this one.

Until the underlying behavior question is settled, please pick one:

  1. test.fails(...) — vitest will pass when the assertion fails, and flip to red if/when the runtime is fixed. Cleanly documents the known-broken behavior.
  2. test.skip(...) with a TODO comment linking to the discussion / issue tracking the race semantics decision.
  3. Drop the failing test from this PR and land just stepWinsRaceWorkflow (which is the meaningful regression-prevention test); open a follow-up issue or PR for the slow-step-vs-fast-sleep case once the semantics are decided.

For reference, the underlying cause is consistent with the V2 inline-execution model in packages/core/src/runtime.ts (workflowEntrypoint): the handler executes the step inline rather than enqueuing it, so a 10s step blocks the handler for 10s and prevents the 1s sleep timer from firing on time. That's a real semantics question worth resolving — but in a separate change.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Resolved — merged latest main, which carried PR #1924's fix for the inline-execution behavior and added these same race tests directly. The duplicate set landed by this branch was dropped (commit 031e4b8), so 99_e2e.ts now matches main exactly and the race tests are no longer part of this PR's diff. Both sleepWinsRaceWorkflow and stepWinsRaceWorkflow now run from main's definitions and pass. This PR is dev-tooling only after the rebase.

Comment threadskills/dev-tmux/SKILL.md Outdated
@@ -0,0 +1,125 @@
---
name: dev-tmux
description: Spin up a 3-pane tmux session for local Workflow SDK development — Next.js turbopack workbench, observability UI, and a scratchpad — all routed through portless so each worktree gets isolated `.localhost` URLs. Use when the user asks to "spin up the dev session", "start dev mode", "set up the workbench", or any time they want to test workflows interactively in a worktree alongside the observability UI.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Activation phrases here are very generic — "spin up the dev session", "start dev mode", "set up the workbench" all describe the general task most contributors do daily, not the specific portless+tmux setup this skill describes. As written, this skill will activate for any contributor asking how to start the dev server, then drive them toward installing portless and adopting a personal tmux layout that the rest of the team doesn't use.

A couple of options:

  1. Narrow the activation phrases to portless-specific terms: "portless dev session", "tmux dev layout", "worktree-isolated dev URLs", etc. That way the skill only fires for users who already know they want this specific tooling.
  2. Move it out of the shared skills/ directory. This appears to be one developer's personal workflow tooling rather than a project-wide convention — skills/migrating-to-workflow-sdk/ is documenting a user-facing capability, whereas this is documenting an internal contributor's local-dev preference. A user-level agent skill (e.g. ~/.agents/skills/dev-tmux/) would keep it personal without affecting other contributors.

(The skill itself is well-written and the troubleshooting section is genuinely useful — this is purely about scope/activation, not quality.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Tightened in 5a44360 (v1.1 of the skill). The description now opens with Spin up a portless + tmux dev session ... and explicitly lists the only triggers as portless dev session / tmux dev layout for workflow / worktree-isolated dev URLs / wanting to wire URLs into the Claude statusline. It also explicitly says not to activate for the generic start the dev server / run pnpm dev task. Should now stay out of any contributor's path who isn't already opting into this tooling.

Kept it under skills/ rather than moving to ~/.claude/ because v1.1 also adds a statusline.sh helper script that needs to live next to the skill — moving it out would split the two artifacts. Open to revisiting if option 2 still feels right.

Comment threadworkbench/nextjs-turbopack/next.config.ts
Worktrees get deleted, so wiring the statusline to a worktree path
breaks the moment the worktree is removed. Update the skill and the
script header to recommend pointing `statusLine.command` at the
primary checkout (`$HOME/github/vercel/workflow/...`). The script
itself is already worktree-aware via Claude's `workspace.current_dir`
stdin JSON, so the same invocation surfaces routes for whichever
worktree the session is in.
Bump version to 1.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment threadskills/dev-tmux/SKILL.md Outdated
Comment on lines +5 to +6
author: Vercel Inc.
version: '1.2'

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

change the author to "Pranay Prakash" and set the version to 0.1 for this first release (since it's a new skill anyway)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 8b7225bmetadata.author: Pranay Prakash, metadata.version: '0.1'.

Comment threadskills/dev-tmux/SKILL.md Outdated
@@ -0,0 +1,160 @@
---
name: dev-tmux

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Suggested change
name: dev-tmux
name: internal-dev-workbench

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Applied in 8b7225bgit mv skills/dev-tmux skills/internal-dev-workbench, plus the matching frontmatter name: change and updates to all internal references in SKILL.md and statusline.sh.

@pranaygp

Copy link
Copy Markdown
ContributorAuthor

making some changes to the skill and testing it a bit more. moving to draft for now

pranaygpand others added 4 commits May 5, 2026 10:50
- Statusline overlay now renders `[dev] · [obs] · tmux:<prefix>`,
with the bracketed labels emitted as OSC 8 hyperlinks (clickable in
any modern terminal) styled cyan + underline so they stand out.
Replaces the old long-URL form that was hard to scan and click.
- Add a tmux-session indicator: shown when a session named exactly the
worktree prefix exists (uses `tmux has-session -t =<prefix>` for
exact matching).
- Change the skill's tmux session naming convention from the fixed
`workflow-dev` to `<worktree-prefix>` (basename of the branch — same
string portless uses as the subdomain prefix). This lets the
statusline locate the session deterministically and lets multiple
worktrees run dev sessions concurrently without manual disambiguation.
- Bump skill to v1.3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the abbreviated `tmux:<prefix>` indicator with the full
copy-paste-ready `tmux attach -t <prefix>` invocation. Saves a step
when grabbing the session from another shell.
Bump skill to v1.4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop the dim styling that made the overlay hard to read; use bold
bright cyan + underline for links and bold bright green for the
tmux command.
- Add Nerd Font glyphs: for dev, for obs, for the
tmux copy-paste hint. Falls back to box-drawing if the font lacks
Nerd Font ranges; layout is unaffected.
- Visual differentiation: cyan + underline = clickable hyperlink;
green = copy this command.
Bump skill to v1.5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The copy glyph in `emit_tmux` was a literal Nerd Font byte embedded in
the printf string and got stripped during a prior rewrite. Promote all
three icons (rocket / graph / copy) to top-level shell variables that
use \uHHHH-equivalent UTF-8 escapes, so the source survives editor
round-trips that don't preserve Private Use Area code points.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pranaygp

Copy link
Copy Markdown
ContributorAuthor

Updates since the previous round of review replies — all in skills/dev-tmux/:

Statusline overhaul (commits c714162938c26e)

  • The statusline command should be wired against the primary checkout ($HOME/github/vercel/workflow/skills/dev-tmux/statusline.sh), not a worktree path that disappears on cleanup. The script remains worktree-aware via Claude's workspace.current_dir stdin JSON, so a single stable path correctly surfaces routes for whichever worktree the session is in.
  • Output format reworked from long URLs to compact OSC 8 hyperlinks: [rocket] dev · [graph] obs · [copy] tmux attach -t <prefix>. The bracketed parts are clickable in iTerm2 / Kitty / WezTerm / Terminal.app / Ghostty (cmd/ctrl-click).
  • Bold + bright styling — bold bright cyan + underline for the clickable links, bold bright green for the tmux attach command (visually distinguishes "click this" from "copy this"). Earlier dim styling was hard to read.
  • Nerd Font icons (octicon rocket, octicon graph, fa copy) defined as top-level shell variables using UTF-8 escapes so they survive editor round-trips that don't preserve PUA code points.

Tmux session naming convention (commit 431026c)

  • The skill now creates the session with <worktree-prefix> (basename of the branch — same string portless uses for the <prefix>.<name>.localhost subdomain) instead of the fixed workflow-dev. Lets multiple worktrees run dev sessions concurrently with no manual disambiguation, and lets the statusline locate the session deterministically via tmux has-session -t =<prefix>.
  • The statusline shows the full tmux attach -t <prefix> invocation (commit b0f373c) so it's directly copy-pasteable into another shell.

Skill version bumped to 1.5. Marking ready for review.

…set version
- Rename `skills/dev-tmux/` → `skills/internal-dev-workbench/` to make
the name self-explanatory about the skill's scope (an internal
contributor's local dev workbench, not a generic tmux helper).
- Author: Pranay Prakash. Version: 0.1 (first release of the skill).
- Update internal references in SKILL.md and statusline.sh accordingly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

4 participants

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

Add dev-tmux skill for portless+tmux local Workflow SDK dev - #1916

Merged
pranaygp merged 11 commits into
mainfrom
pgp/stepflow-test
May 5, 2026
Merged

Add dev-tmux skill for portless+tmux local Workflow SDK dev#1916
pranaygp merged 11 commits into
mainfrom
pgp/stepflow-test

Conversation

@pranaygp

@pranaygppranaygp commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • New dev-tmux skill documenting the 3-pane tmux + portless setup for local Workflow SDK development (turbopack workbench, observability UI scoped to that workbench, scratchpad shell — all worktree-isolated via portless's branch-prefixed .localhost URLs).
  • Adds allowedDevOrigins: ['turbopack.localhost', '*.turbopack.localhost'] to workbench/nextjs-turbopack/next.config.ts so portless-style URLs don't get blocked by Next's cross-origin protection in dev (currently floods the logs with HMR warnings).

What changed since the original PR

The original PR also added two race workflows (sleepWinsRaceWorkflow, stepWinsRaceWorkflow) and their e2e tests to surface a Promise.race-vs-replay semantics issue. Those landed independently in #1924 (with the underlying fix), so this branch was rebased onto main and the duplicate additions removed — the race tests now come from main. This PR is dev-tooling only.

Test plan

  • pnpm vitest run packages/core/e2e/e2e.test.ts -t WinsRaceWorkflow against the turbopack workbench (passes after rebase, since [core] V2: skip inline step execution when suspension also has a wait #1924's fix is on main)
  • Spin up the workbench by following skills/dev-tmux/SKILL.md and confirm both https://<branch>.turbopack.localhost (HMR works, no cross-origin warnings) and https://<branch>.workflow-obs.localhost (observability UI shows runs from the workbench)

🤖 Generated with Claude Code

Adds two race workflows (sleepWinsRaceWorkflow, stepWinsRaceWorkflow)
that exercise Promise.race between a step function and a sleep call.
The current `sleepWinsRaceWorkflow` test fails — surfacing how the
replay engine resolves a previously-completed step instantly while
sleep still has to elapse.
Also adds a `dev-tmux` skill that documents the 3-pane tmux + portless
setup for testing workflows interactively in a worktree alongside the
observability UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings May 4, 2026 11:48
@changeset-bot

changeset-botBot commented May 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8b7225b

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 May 4, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 4, 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🥇 Nitro0.031s (-27.8% 🟢)1.005s (~)0.974s101.00x
💻 LocalExpress0.034s (-22.3% 🟢)1.005s (~)0.970s101.11x
🐘 PostgresExpress0.046s (-20.7% 🟢)1.012s (~)0.966s101.48x
🐘 PostgresNitro0.046s (-51.7% 🟢)1.012s (-3.0%)0.966s101.48x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.315s (-23.1% 🟢)2.072s (-17.4% 🟢)1.758s101.00x
▲ VercelNext.js (Turbopack)0.793s (+215.3% 🔺)2.620s (+12.3% 🔺)1.827s102.52x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.068s (-5.1% 🟢)2.006s (~)0.938s101.00x
💻 LocalNitro1.073s (-5.2% 🟢)2.006s (~)0.933s101.00x
🐘 PostgresNitro1.083s (-5.0%)2.011s (~)0.927s101.01x
🐘 PostgresExpress1.085s (-5.4% 🟢)2.010s (~)0.925s101.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.777s (-54.3% 🟢)3.967s (-32.9% 🟢)2.190s101.00x
▲ VercelNext.js (Turbopack)2.267s (+11.4% 🔺)3.754s (-2.0%)1.487s101.28x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.402s (-4.8%)11.020s (~)0.618s31.00x
🐘 PostgresNitro10.402s (-4.3%)11.017s (~)0.615s31.00x
💻 LocalNitro10.414s (-4.9%)11.022s (~)0.607s31.00x
🐘 PostgresExpress10.446s (-4.7%)11.019s (~)0.574s31.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.463s (-43.3% 🟢)15.032s (-40.2% 🟢)1.568s21.00x
▲ VercelNext.js (Turbopack)14.098s (-18.6% 🟢)15.843s (-18.3% 🟢)1.745s21.05x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.436s (-10.2% 🟢)14.025s (-6.7% 🟢)0.589s51.00x
🐘 PostgresNitro13.445s (-7.9% 🟢)14.018s (-6.7% 🟢)0.573s51.00x
💻 LocalNitro13.481s (-10.5% 🟢)14.027s (-12.5% 🟢)0.546s51.00x
🐘 PostgresExpress13.493s (-7.5% 🟢)14.024s (-6.6% 🟢)0.531s51.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro21.085s (-67.3% 🟢)23.044s (-65.4% 🟢)1.959s31.00x
▲ VercelNext.js (Turbopack)22.401s (-57.4% 🟢)24.203s (-55.7% 🟢)1.802s31.06x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro11.880s (-14.9% 🟢)12.016s (-16.0% 🟢)0.136s81.00x
💻 LocalNitro11.939s (-28.9% 🟢)12.147s (-28.7% 🟢)0.208s81.00x
💻 LocalExpress11.974s (-27.9% 🟢)12.396s (-27.2% 🟢)0.422s81.01x
🐘 PostgresExpress12.130s (-13.4% 🟢)12.642s (-13.4% 🟢)0.512s81.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro30.301s (-92.8% 🟢)32.393s (-92.4% 🟢)2.092s31.00x
▲ VercelNext.js (Turbopack)34.399s (-91.3% 🟢)36.050s (-90.9% 🟢)1.651s31.14x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.156s (-22.3% 🟢)2.005s (~)0.849s151.00x
🐘 PostgresNitro1.159s (-9.1% 🟢)2.008s (~)0.849s151.00x
🐘 PostgresExpress1.160s (-8.0% 🟢)2.008s (~)0.848s151.00x
💻 LocalNitro1.180s (-27.6% 🟢)2.006s (-3.3%)0.825s151.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.412s (-14.4% 🟢)3.982s (-7.9% 🟢)1.570s81.00x
▲ VercelNext.js (Turbopack)4.906s (+44.4% 🔺)6.610s (+34.0% 🔺)1.704s52.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.228s (-48.0% 🟢)2.008s (-33.3% 🟢)0.780s151.00x
🐘 PostgresNitro1.238s (-47.3% 🟢)2.006s (-33.3% 🟢)0.768s151.01x
💻 LocalExpress1.598s (-45.9% 🟢)2.005s (-41.9% 🟢)0.407s151.30x
💻 LocalNitro1.894s (-39.8% 🟢)2.149s (-44.7% 🟢)0.256s141.54x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.300s (-18.6% 🟢)5.165s (-12.8% 🟢)1.865s61.00x
▲ VercelNext.js (Turbopack)5.590s (-21.3% 🟢)7.240s (-18.7% 🟢)1.651s51.69x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.380s (-60.4% 🟢)2.009s (-49.9% 🟢)0.629s151.00x
🐘 PostgresNitro1.390s (-60.0% 🟢)2.008s (-49.9% 🟢)0.617s151.01x
💻 LocalExpress4.503s (-46.0% 🟢)4.725s (-47.7% 🟢)0.222s73.26x
💻 LocalNitro5.496s (-34.2% 🟢)6.014s (-33.3% 🟢)0.518s53.98x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)5.990s (-32.8% 🟢)7.487s (-31.7% 🟢)1.496s51.00x
▲ VercelNitro6.899s (+95.7% 🔺)10.766s (+94.6% 🔺)3.867s31.15x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.159s (-7.8% 🟢)2.009s (~)0.850s151.00x
🐘 PostgresNitro1.165s (-7.4% 🟢)2.009s (~)0.844s151.00x
💻 LocalExpress1.317s (-30.5% 🟢)2.005s (-15.2% 🟢)0.689s151.14x
💻 LocalNitro1.415s (-24.1% 🟢)2.007s (-14.3% 🟢)0.591s151.22x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.462s (~)3.978s (-4.6%)1.516s81.00x
▲ VercelNext.js (Turbopack)5.008s (+70.8% 🔺)6.602s (+42.2% 🔺)1.594s52.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.221s (-47.8% 🟢)2.009s (-33.3% 🟢)0.788s151.00x
🐘 PostgresExpress1.241s (-47.0% 🟢)2.008s (-33.3% 🟢)0.767s151.02x
💻 LocalExpress1.666s (-46.8% 🟢)2.005s (-46.7% 🟢)0.339s151.36x
💻 LocalNitro2.096s (-31.6% 🟢)2.592s (-33.3% 🟢)0.496s121.72x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.255s (+0.7%)5.219s (+2.8%)1.965s61.00x
▲ VercelNext.js (Turbopack)4.456s (+41.8% 🔺)6.286s (+39.0% 🔺)1.830s51.37x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.358s (-61.0% 🟢)2.007s (-49.9% 🟢)0.649s151.00x
🐘 PostgresExpress1.379s (-60.6% 🟢)2.008s (-49.9% 🟢)0.628s151.02x
💻 LocalExpress4.938s (-43.9% 🟢)5.513s (-40.6% 🟢)0.575s63.64x
💻 LocalNitro6.426s (-29.7% 🟢)6.819s (-32.0% 🟢)0.393s54.73x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.698s (-7.8% 🟢)6.786s (~)2.088s51.00x
▲ VercelNext.js (Turbopack)5.481s (-18.9% 🟢)7.171s (-16.1% 🟢)1.689s51.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.429s (-47.8% 🟢)1.006s (~)0.578s601.00x
🐘 PostgresExpress0.435s (-48.2% 🟢)1.007s (-1.6%)0.572s601.01x
💻 LocalExpress0.444s (-54.8% 🟢)1.004s (-6.7% 🟢)0.559s601.04x
💻 LocalNitro0.471s (-52.0% 🟢)1.004s (-8.2% 🟢)0.534s601.10x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.925s (-77.7% 🟢)7.020s (-70.8% 🟢)2.095s91.00x
▲ VercelNext.js (Turbopack)6.772s (-53.3% 🟢)8.207s (-49.0% 🟢)1.435s81.37x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.028s (-46.7% 🟢)1.413s (-32.7% 🟢)0.384s641.00x
🐘 PostgresExpress1.065s (-46.1% 🟢)1.771s (-21.5% 🟢)0.706s511.04x
💻 LocalExpress1.125s (-62.7% 🟢)2.005s (-44.1% 🟢)0.880s451.09x
💻 LocalNitro1.184s (-61.0% 🟢)2.006s (-46.6% 🟢)0.822s451.15x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro12.382s (-68.6% 🟢)14.665s (-64.5% 🟢)2.283s71.00x
▲ VercelNext.js (Turbopack)15.639s (-68.6% 🟢)17.695s (-65.8% 🟢)2.056s61.26x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.974s (-51.9% 🟢)2.380s (-48.3% 🟢)0.405s511.00x
🐘 PostgresExpress2.076s (-48.0% 🟢)2.529s (-42.1% 🟢)0.453s481.05x
💻 LocalExpress2.545s (-72.4% 🟢)3.032s (-69.7% 🟢)0.488s401.29x
💻 LocalNitro2.664s (-71.4% 🟢)3.057s (-69.5% 🟢)0.394s401.35x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro38.936s (-59.8% 🟢)41.471s (-57.9% 🟢)2.535s41.00x
▲ VercelNext.js (Turbopack)45.359s (-57.7% 🟢)47.056s (-56.8% 🟢)1.697s31.16x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.186s (-34.3% 🟢)1.005s (~)0.819s601.00x
🐘 PostgresExpress0.200s (-29.0% 🟢)1.006s (~)0.806s601.08x
💻 LocalExpress0.389s (-30.6% 🟢)1.004s (~)0.615s602.09x
💻 LocalNitro0.441s (-27.1% 🟢)1.004s (-1.7%)0.563s602.37x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.370s (+42.7% 🔺)3.938s (+17.5% 🔺)1.568s161.00x
▲ VercelNext.js (Turbopack)3.739s (+84.9% 🔺)5.211s (+37.4% 🔺)1.473s121.58x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.307s (-38.1% 🟢)1.006s (~)0.699s901.00x
🐘 PostgresExpress0.329s (-35.5% 🟢)1.006s (~)0.678s901.07x
💻 LocalExpress2.111s (-16.0% 🟢)2.684s (-10.8% 🟢)0.573s346.88x
💻 LocalNitro2.201s (-13.3% 🟢)2.945s (-2.1%)0.744s317.17x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.219s (+123.8% 🔺)9.007s (+86.8% 🔺)1.788s101.00x
▲ VercelNext.js (Turbopack)7.230s (+104.5% 🔺)8.888s (+71.1% 🔺)1.657s111.00x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.623s (-21.2% 🟢)1.006s (~)0.383s1201.00x
🐘 PostgresExpress0.676s (-17.4% 🟢)1.007s (-1.1%)0.330s1201.09x
💻 LocalExpress8.906s (-20.4% 🟢)9.485s (-20.6% 🟢)0.579s1314.30x
💻 LocalNitro10.253s (-8.4% 🟢)10.694s (-8.3% 🟢)0.441s1216.47x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)22.466s (+117.5% 🔺)23.969s (+95.1% 🔺)1.504s61.00x
▲ VercelNitro24.505s (+217.3% 🔺)26.705s (+184.1% 🔺)2.200s51.09x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.120s (+462.6% 🔺)2.004s (+99.5% 🔺)0.009s (-22.3% 🟢)2.016s (+98.0% 🔺)0.896s101.00x
💻 LocalNitro1.134s (+430.7% 🔺)2.006s (+99.7% 🔺)0.012s (-0.8%)2.020s (+98.3% 🔺)0.886s101.01x
🐘 PostgresNitro1.140s (+456.2% 🔺)2.002s (+100.3% 🔺)0.001s (-13.3% 🟢)2.011s (+98.8% 🔺)0.871s101.02x
🐘 PostgresExpress1.159s (+465.2% 🔺)1.998s (+100.1% 🔺)0.001s (-12.5% 🟢)2.011s (+98.8% 🔺)0.852s101.03x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.439s (-36.4% 🟢)3.535s (-33.0% 🟢)2.339s (+215.2% 🔺)6.477s (~)4.038s101.00x
▲ VercelNext.js (Turbopack)4.213s (-38.5% 🟢)4.295s (-50.3% 🟢)1.310s (+107.3% 🔺)7.294s (-25.5% 🟢)3.081s101.73x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.488s (+138.4% 🔺)2.003s (+99.0% 🔺)0.004s (-3.3%)2.025s (+98.0% 🔺)0.536s301.00x
💻 LocalNitro1.523s (+81.6% 🔺)2.012s (+98.8% 🔺)0.010s (+5.3% 🔺)2.023s (+81.3% 🔺)0.500s301.02x
🐘 PostgresExpress1.540s (+144.4% 🔺)2.009s (+99.6% 🔺)0.004s (+3.6%)2.026s (+98.0% 🔺)0.486s301.03x
💻 LocalExpress1.665s (+119.9% 🔺)2.009s (+95.2% 🔺)0.009s (-1.0%)2.199s (+111.5% 🔺)0.534s281.12x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.887s (-80.0% 🟢)7.416s (-75.9% 🟢)0.184s (+64.0% 🔺)8.100s (-74.5% 🟢)2.213s81.00x
▲ VercelNext.js (Turbopack)13.623s (-19.5% 🟢)13.512s (-25.9% 🟢)0.199s (-5.7% 🟢)15.368s (-18.8% 🟢)1.744s42.31x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.653s (-32.6% 🟢)1.050s (-15.9% 🟢)0.000s (-57.9% 🟢)1.060s (-15.7% 🟢)0.407s571.00x
🐘 PostgresExpress0.672s (-30.0% 🟢)1.033s (-19.2% 🟢)0.000s (+19.0% 🔺)1.042s (-20.2% 🟢)0.370s581.03x
💻 LocalExpress1.274s (+4.0%)2.013s (~)0.000s (-40.0% 🟢)2.015s (~)0.740s301.95x
💻 LocalNitro1.310s (+7.1% 🔺)2.017s (~)0.001s (+400.0% 🔺)2.019s (~)0.709s302.01x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.313s (+8.6% 🔺)4.592s (+4.5%)0.000s (+8.3% 🔺)5.023s (+4.5%)1.710s121.00x
▲ VercelNext.js (Turbopack)5.512s (-45.9% 🟢)6.062s (-47.4% 🟢)0.000s (+Infinity% 🔺)6.982s (-42.1% 🟢)1.470s91.66x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.168s (-34.8% 🟢)1.996s (-6.8% 🟢)0.000s (-100.0% 🟢)2.026s (-6.8% 🟢)0.858s301.00x
🐘 PostgresExpress1.377s (-22.3% 🟢)2.066s (-5.1% 🟢)0.000s (NaN%)2.082s (-5.3% 🟢)0.705s291.18x
💻 LocalExpress2.968s (-14.4% 🟢)3.555s (-11.9% 🟢)0.000s (-70.6% 🟢)3.559s (-11.8% 🟢)0.591s172.54x
💻 LocalNitro3.084s (-9.0% 🟢)3.781s (-6.2% 🟢)0.000s (-29.7% 🟢)3.783s (-6.3% 🟢)0.700s162.64x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.124s (+25.2% 🔺)6.356s (+18.3% 🔺)0.000s (-100.0% 🟢)6.813s (+17.6% 🔺)1.689s91.00x
▲ VercelNext.js (Turbopack)7.535s (+34.2% 🔺)8.909s (+27.6% 🔺)0.000s (-100.0% 🟢)9.344s (+23.9% 🔺)1.809s71.47x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress18/21
🐘 PostgresNitro17/21
▲ VercelNitro19/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

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

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actionsBot commented May 4, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production92502191144
✅ 💻 Local Development123702191456
✅ 📦 Local Production123702191456
✅ 🐘 Local Postgres123702191456
✅ 🪟 Windows10400104
✅ 📋 Other5520176728
Total5292010526344

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro78026
✅ example78026
✅ express78026
✅ fastify78026
✅ hono78026
✅ nextjs-turbopack10202
✅ nextjs-webpack10202
✅ nitro78026
✅ nuxt78026
✅ sveltekit9707
✅ vite78026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack10400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable79025
✅ e2e-local-dev-tanstack-start-79025
✅ e2e-local-postgres-nest-stable79025
✅ e2e-local-postgres-tanstack-start-79025
✅ e2e-local-prod-nest-stable79025
✅ e2e-local-prod-tanstack-start-79025
✅ e2e-vercel-prod-tanstack-start78026

📋 View full workflow run

Adds allowedDevOrigins entries so portless-style worktree-prefixed
.localhost URLs (e.g. https://<branch>.turbopack.localhost) can hit
HMR and dev-only endpoints without Next's cross-origin protection
flooding the logs with warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

CopilotAI 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.

Pull request overview

Adds new end-to-end coverage for Promise.race behavior between a long-running step and sleep(), and introduces a new dev-tmux skill to standardize a local 3-pane dev setup (workbench + observability UI + scratchpad) using portless for worktree-isolated .localhost URLs.

Changes:

  • Add sleepWinsRaceWorkflow / stepWinsRaceWorkflow workflows to the workbench e2e workflow collection.
  • Add corresponding Vitest e2e tests asserting the expected race winner and duration bounds.
  • Add skills/dev-tmux/SKILL.md documentation for a tmux + portless local dev workflow; add a (currently empty) changeset file.

Reviewed changes

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

FileDescription
workbench/example/workflows/99_e2e.tsAdds two new workflows plus a shared step helper to exercise Promise.race between a step and sleep().
packages/core/e2e/e2e.test.tsAdds two e2e tests covering the new race workflows and asserting winner + duration.
skills/dev-tmux/SKILL.mdNew skill documenting a portless-routed 3-pane tmux setup for local SDK dev + e2e runs.
.changeset/better-pets-reply.mdAdds a new changeset file, but it is currently empty/invalid.

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

Comment on lines +584 to +590
test('sleepWinsRaceWorkflow', { timeout: 60_000 }, async () => {
const run = await start(await e2e('sleepWinsRaceWorkflow'), []);
const returnValue = await run.returnValue;
expect(returnValue.winner).toBe('sleep');
// Sleep is 1s; step would take 10s. Should resolve in ~1s, well under 5s.
expect(returnValue.durationMs).toBeLessThan(5_000);
});

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Obsolete after rebase onto main — PR #1924 fixed the underlying race semantics and added these same tests directly to main. The duplicate set on this branch was dropped (031e4b8); the race tests are no longer part of this PR's diff.

Comment thread.changeset/better-pets-reply.md
Comment threadskills/dev-tmux/SKILL.md Outdated
Comment on lines +51 to +59
tmux send-keys -t "$SESSION".1 \
'cd workbench/nextjs-turbopack && WORKFLOW_PUBLIC_MANIFEST=1 portless run --name turbopack pnpm dev' C-m

# Pane 2 (top-right): observability UI scoped to the workbench app
tmux send-keys -t "$SESSION".2 \
'cd workbench/nextjs-turbopack && portless run --name workflow-obs sh -c "pnpm workflow web --webPort \$PORT --noBrowser"' C-m

# Pane 3 (bottom-right): scratchpad at repo root
tmux send-keys -t "$SESSION".3 'echo "scratchpad: $(pwd)"' C-m

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Fixed in 5a44360. The skill now captures each pane's ID at split time with -P -F '#{pane_id}' and uses those IDs as targets, so it's correct under both pane-base-index 0 and pane-base-index 1 regardless of the user's tmux config.

Comment threadpackages/core/e2e/e2e.test.ts
TooTallNate
TooTallNate previously requested changes May 4, 2026

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verdict: Request Changes

The PR has two things in it: e2e race tests (good idea, surfacing a real V2-handler semantics question) and a personal-dev skill file. I have one blocker on the test, one scope concern on the skill, and one minor nit on the next.config change.


Summary of inline comments

  1. packages/core/e2e/e2e.test.ts:587 — Blocker.sleepWinsRaceWorkflow fails on every e2e job (56+ jobs across all frameworks × all environments). Needs test.fails(...), .skip with TODO, or removal until the underlying race semantics are decided. The PR description correctly identifies this as intentional pending discussion, but the current state makes the e2e matrix unusable for any PR based on this one.

  2. skills/dev-tmux/SKILL.md:3 — Scope. Activation phrases ("spin up the dev session", "start dev mode", "set up the workbench") will trigger for general contributor questions, then push them toward installing portless. Either narrow the phrases to portless-specific terms or move the skill to a user-level location.

  3. workbench/nextjs-turbopack/next.config.ts:13 — Nit. Hardcoding *.turbopack.localhost in the workbench bakes one developer's convention into the shared config. Dev-only, so harmless — flagging for awareness, not blocking.


What I verified

  • Diagnosis is correct: The V2 inline-execution model in workflowEntrypoint (packages/core/src/runtime.ts) executes step bodies inline rather than enqueueing them. A 10-second delayMsStep blocks the handler for 10 seconds, which prevents the 1-second sleep('1s') timer from firing on time. So Promise.race ends up resolving with 'step' because that's what the handler delivers first. Whether this should be considered correct behavior or a bug is a real question.
  • Symlinks: Confirmed 99_e2e.ts is symlinked from workbench/example/workflows/99_e2e.ts into all other workbenches per AGENTS.md convention. ✅
  • Empty changeset: ---\n--- is the right pattern for tests-only PRs that don't ship package changes; matches existing examples (fix-vite-step-source-maps-e2e.md, tanstack-start-workbench.md). ✅
  • Skill frontmatter: version: '1.0' is fine for a new skill (existing migrating-to-workflow-sdk is '0.2.0'). Format matches.
  • Typecheck: Clean across all 40 packages.

Suggested path forward

Smallest change that keeps the value of this PR:

  1. Mark sleepWinsRaceWorkflow with test.fails(...) and add a code comment linking to an issue tracking the V2 race-semantics decision. That preserves the test as a regression detector — if the underlying behavior is ever fixed, the test will go red and force a follow-up.
  2. Optional: open a separate small skill PR after clarifying scope, or move the skill to a personal location.
  3. Land stepWinsRaceWorkflow as a regular passing test — it's a useful regression check on the easy direction.

Happy to re-review once the failing assertion is addressed.

test('sleepWinsRaceWorkflow', { timeout: 60_000 }, async () => {
const run = await start(await e2e('sleepWinsRaceWorkflow'), []);
const returnValue = await run.returnValue;
expect(returnValue.winner).toBe('sleep');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocker — every e2e CI job is failing because of this assertion.

This test fails on every framework's e2e run (express, hono, nitro, fastify, astro, vite, nuxt, sveltekit, tanstack-start, both Next.js variants — local-dev, local-prod, local-postgres, AND vercel-prod) because 99_e2e.ts is shared across all workbenches via symlink. As of the latest run, that's 56+ failed jobs all on expected 'step' to be 'sleep'.

For example: E2E Local Dev Tests (express - stable)FAIL packages/core/e2e/e2e.test.ts > e2e > sleepWinsRaceWorkflow / AssertionError: expected 'step' to be 'sleep'.

The PR description acknowledges this is intentional and wants discussion before merge. That's reasonable as a discussion vehicle, but it can't merge in this state — the PR effectively makes the entire e2e matrix red for any other PR that bases on this one.

Until the underlying behavior question is settled, please pick one:

  1. test.fails(...) — vitest will pass when the assertion fails, and flip to red if/when the runtime is fixed. Cleanly documents the known-broken behavior.
  2. test.skip(...) with a TODO comment linking to the discussion / issue tracking the race semantics decision.
  3. Drop the failing test from this PR and land just stepWinsRaceWorkflow (which is the meaningful regression-prevention test); open a follow-up issue or PR for the slow-step-vs-fast-sleep case once the semantics are decided.

For reference, the underlying cause is consistent with the V2 inline-execution model in packages/core/src/runtime.ts (workflowEntrypoint): the handler executes the step inline rather than enqueuing it, so a 10s step blocks the handler for 10s and prevents the 1s sleep timer from firing on time. That's a real semantics question worth resolving — but in a separate change.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Resolved — merged latest main, which carried PR #1924's fix for the inline-execution behavior and added these same race tests directly. The duplicate set landed by this branch was dropped (commit 031e4b8), so 99_e2e.ts now matches main exactly and the race tests are no longer part of this PR's diff. Both sleepWinsRaceWorkflow and stepWinsRaceWorkflow now run from main's definitions and pass. This PR is dev-tooling only after the rebase.

Comment threadskills/dev-tmux/SKILL.md Outdated
@@ -0,0 +1,125 @@
---
name: dev-tmux
description: Spin up a 3-pane tmux session for local Workflow SDK development — Next.js turbopack workbench, observability UI, and a scratchpad — all routed through portless so each worktree gets isolated `.localhost` URLs. Use when the user asks to "spin up the dev session", "start dev mode", "set up the workbench", or any time they want to test workflows interactively in a worktree alongside the observability UI.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Activation phrases here are very generic — "spin up the dev session", "start dev mode", "set up the workbench" all describe the general task most contributors do daily, not the specific portless+tmux setup this skill describes. As written, this skill will activate for any contributor asking how to start the dev server, then drive them toward installing portless and adopting a personal tmux layout that the rest of the team doesn't use.

A couple of options:

  1. Narrow the activation phrases to portless-specific terms: "portless dev session", "tmux dev layout", "worktree-isolated dev URLs", etc. That way the skill only fires for users who already know they want this specific tooling.
  2. Move it out of the shared skills/ directory. This appears to be one developer's personal workflow tooling rather than a project-wide convention — skills/migrating-to-workflow-sdk/ is documenting a user-facing capability, whereas this is documenting an internal contributor's local-dev preference. A user-level agent skill (e.g. ~/.agents/skills/dev-tmux/) would keep it personal without affecting other contributors.

(The skill itself is well-written and the troubleshooting section is genuinely useful — this is purely about scope/activation, not quality.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Tightened in 5a44360 (v1.1 of the skill). The description now opens with Spin up a portless + tmux dev session ... and explicitly lists the only triggers as portless dev session / tmux dev layout for workflow / worktree-isolated dev URLs / wanting to wire URLs into the Claude statusline. It also explicitly says not to activate for the generic start the dev server / run pnpm dev task. Should now stay out of any contributor's path who isn't already opting into this tooling.

Kept it under skills/ rather than moving to ~/.claude/ because v1.1 also adds a statusline.sh helper script that needs to live next to the skill — moving it out would split the two artifacts. Open to revisiting if option 2 still feels right.

Comment threadworkbench/nextjs-turbopack/next.config.ts
Worktrees get deleted, so wiring the statusline to a worktree path
breaks the moment the worktree is removed. Update the skill and the
script header to recommend pointing `statusLine.command` at the
primary checkout (`$HOME/github/vercel/workflow/...`). The script
itself is already worktree-aware via Claude's `workspace.current_dir`
stdin JSON, so the same invocation surfaces routes for whichever
worktree the session is in.
Bump version to 1.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment threadskills/dev-tmux/SKILL.md Outdated
Comment on lines +5 to +6
author: Vercel Inc.
version: '1.2'

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

change the author to "Pranay Prakash" and set the version to 0.1 for this first release (since it's a new skill anyway)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 8b7225bmetadata.author: Pranay Prakash, metadata.version: '0.1'.

Comment threadskills/dev-tmux/SKILL.md Outdated
@@ -0,0 +1,160 @@
---
name: dev-tmux

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Suggested change
name: dev-tmux
name: internal-dev-workbench

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Applied in 8b7225bgit mv skills/dev-tmux skills/internal-dev-workbench, plus the matching frontmatter name: change and updates to all internal references in SKILL.md and statusline.sh.

@pranaygp

Copy link
Copy Markdown
ContributorAuthor

making some changes to the skill and testing it a bit more. moving to draft for now

pranaygpand others added 4 commits May 5, 2026 10:50
- Statusline overlay now renders `[dev] · [obs] · tmux:<prefix>`,
with the bracketed labels emitted as OSC 8 hyperlinks (clickable in
any modern terminal) styled cyan + underline so they stand out.
Replaces the old long-URL form that was hard to scan and click.
- Add a tmux-session indicator: shown when a session named exactly the
worktree prefix exists (uses `tmux has-session -t =<prefix>` for
exact matching).
- Change the skill's tmux session naming convention from the fixed
`workflow-dev` to `<worktree-prefix>` (basename of the branch — same
string portless uses as the subdomain prefix). This lets the
statusline locate the session deterministically and lets multiple
worktrees run dev sessions concurrently without manual disambiguation.
- Bump skill to v1.3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the abbreviated `tmux:<prefix>` indicator with the full
copy-paste-ready `tmux attach -t <prefix>` invocation. Saves a step
when grabbing the session from another shell.
Bump skill to v1.4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop the dim styling that made the overlay hard to read; use bold
bright cyan + underline for links and bold bright green for the
tmux command.
- Add Nerd Font glyphs: for dev, for obs, for the
tmux copy-paste hint. Falls back to box-drawing if the font lacks
Nerd Font ranges; layout is unaffected.
- Visual differentiation: cyan + underline = clickable hyperlink;
green = copy this command.
Bump skill to v1.5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The copy glyph in `emit_tmux` was a literal Nerd Font byte embedded in
the printf string and got stripped during a prior rewrite. Promote all
three icons (rocket / graph / copy) to top-level shell variables that
use \uHHHH-equivalent UTF-8 escapes, so the source survives editor
round-trips that don't preserve Private Use Area code points.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pranaygp

Copy link
Copy Markdown
ContributorAuthor

Updates since the previous round of review replies — all in skills/dev-tmux/:

Statusline overhaul (commits c714162938c26e)

  • The statusline command should be wired against the primary checkout ($HOME/github/vercel/workflow/skills/dev-tmux/statusline.sh), not a worktree path that disappears on cleanup. The script remains worktree-aware via Claude's workspace.current_dir stdin JSON, so a single stable path correctly surfaces routes for whichever worktree the session is in.
  • Output format reworked from long URLs to compact OSC 8 hyperlinks: [rocket] dev · [graph] obs · [copy] tmux attach -t <prefix>. The bracketed parts are clickable in iTerm2 / Kitty / WezTerm / Terminal.app / Ghostty (cmd/ctrl-click).
  • Bold + bright styling — bold bright cyan + underline for the clickable links, bold bright green for the tmux attach command (visually distinguishes "click this" from "copy this"). Earlier dim styling was hard to read.
  • Nerd Font icons (octicon rocket, octicon graph, fa copy) defined as top-level shell variables using UTF-8 escapes so they survive editor round-trips that don't preserve PUA code points.

Tmux session naming convention (commit 431026c)

  • The skill now creates the session with <worktree-prefix> (basename of the branch — same string portless uses for the <prefix>.<name>.localhost subdomain) instead of the fixed workflow-dev. Lets multiple worktrees run dev sessions concurrently with no manual disambiguation, and lets the statusline locate the session deterministically via tmux has-session -t =<prefix>.
  • The statusline shows the full tmux attach -t <prefix> invocation (commit b0f373c) so it's directly copy-pasteable into another shell.

Skill version bumped to 1.5. Marking ready for review.

…set version
- Rename `skills/dev-tmux/` → `skills/internal-dev-workbench/` to make
the name self-explanatory about the skill's scope (an internal
contributor's local dev workbench, not a generic tmux helper).
- Author: Pranay Prakash. Version: 0.1 (first release of the skill).
- Update internal references in SKILL.md and statusline.sh accordingly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

4 participants

@pranaygp@TooTallNate@VaguelySerious
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add dev-tmux skill for portless+tmux local Workflow SDK dev - #1916

Merged
pranaygp merged 11 commits into
mainfrom
pgp/stepflow-test
May 5, 2026
Merged

Add dev-tmux skill for portless+tmux local Workflow SDK dev#1916
pranaygp merged 11 commits into
mainfrom
pgp/stepflow-test

Conversation

@pranaygp

@pranaygppranaygp commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • New dev-tmux skill documenting the 3-pane tmux + portless setup for local Workflow SDK development (turbopack workbench, observability UI scoped to that workbench, scratchpad shell — all worktree-isolated via portless's branch-prefixed .localhost URLs).
  • Adds allowedDevOrigins: ['turbopack.localhost', '*.turbopack.localhost'] to workbench/nextjs-turbopack/next.config.ts so portless-style URLs don't get blocked by Next's cross-origin protection in dev (currently floods the logs with HMR warnings).

What changed since the original PR

The original PR also added two race workflows (sleepWinsRaceWorkflow, stepWinsRaceWorkflow) and their e2e tests to surface a Promise.race-vs-replay semantics issue. Those landed independently in #1924 (with the underlying fix), so this branch was rebased onto main and the duplicate additions removed — the race tests now come from main. This PR is dev-tooling only.

Test plan

  • pnpm vitest run packages/core/e2e/e2e.test.ts -t WinsRaceWorkflow against the turbopack workbench (passes after rebase, since [core] V2: skip inline step execution when suspension also has a wait #1924's fix is on main)
  • Spin up the workbench by following skills/dev-tmux/SKILL.md and confirm both https://<branch>.turbopack.localhost (HMR works, no cross-origin warnings) and https://<branch>.workflow-obs.localhost (observability UI shows runs from the workbench)

🤖 Generated with Claude Code

Adds two race workflows (sleepWinsRaceWorkflow, stepWinsRaceWorkflow)
that exercise Promise.race between a step function and a sleep call.
The current `sleepWinsRaceWorkflow` test fails — surfacing how the
replay engine resolves a previously-completed step instantly while
sleep still has to elapse.
Also adds a `dev-tmux` skill that documents the 3-pane tmux + portless
setup for testing workflows interactively in a worktree alongside the
observability UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings May 4, 2026 11:48
@changeset-bot

changeset-botBot commented May 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8b7225b

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 May 4, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 4, 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🥇 Nitro0.031s (-27.8% 🟢)1.005s (~)0.974s101.00x
💻 LocalExpress0.034s (-22.3% 🟢)1.005s (~)0.970s101.11x
🐘 PostgresExpress0.046s (-20.7% 🟢)1.012s (~)0.966s101.48x
🐘 PostgresNitro0.046s (-51.7% 🟢)1.012s (-3.0%)0.966s101.48x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.315s (-23.1% 🟢)2.072s (-17.4% 🟢)1.758s101.00x
▲ VercelNext.js (Turbopack)0.793s (+215.3% 🔺)2.620s (+12.3% 🔺)1.827s102.52x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.068s (-5.1% 🟢)2.006s (~)0.938s101.00x
💻 LocalNitro1.073s (-5.2% 🟢)2.006s (~)0.933s101.00x
🐘 PostgresNitro1.083s (-5.0%)2.011s (~)0.927s101.01x
🐘 PostgresExpress1.085s (-5.4% 🟢)2.010s (~)0.925s101.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.777s (-54.3% 🟢)3.967s (-32.9% 🟢)2.190s101.00x
▲ VercelNext.js (Turbopack)2.267s (+11.4% 🔺)3.754s (-2.0%)1.487s101.28x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.402s (-4.8%)11.020s (~)0.618s31.00x
🐘 PostgresNitro10.402s (-4.3%)11.017s (~)0.615s31.00x
💻 LocalNitro10.414s (-4.9%)11.022s (~)0.607s31.00x
🐘 PostgresExpress10.446s (-4.7%)11.019s (~)0.574s31.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.463s (-43.3% 🟢)15.032s (-40.2% 🟢)1.568s21.00x
▲ VercelNext.js (Turbopack)14.098s (-18.6% 🟢)15.843s (-18.3% 🟢)1.745s21.05x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.436s (-10.2% 🟢)14.025s (-6.7% 🟢)0.589s51.00x
🐘 PostgresNitro13.445s (-7.9% 🟢)14.018s (-6.7% 🟢)0.573s51.00x
💻 LocalNitro13.481s (-10.5% 🟢)14.027s (-12.5% 🟢)0.546s51.00x
🐘 PostgresExpress13.493s (-7.5% 🟢)14.024s (-6.6% 🟢)0.531s51.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro21.085s (-67.3% 🟢)23.044s (-65.4% 🟢)1.959s31.00x
▲ VercelNext.js (Turbopack)22.401s (-57.4% 🟢)24.203s (-55.7% 🟢)1.802s31.06x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro11.880s (-14.9% 🟢)12.016s (-16.0% 🟢)0.136s81.00x
💻 LocalNitro11.939s (-28.9% 🟢)12.147s (-28.7% 🟢)0.208s81.00x
💻 LocalExpress11.974s (-27.9% 🟢)12.396s (-27.2% 🟢)0.422s81.01x
🐘 PostgresExpress12.130s (-13.4% 🟢)12.642s (-13.4% 🟢)0.512s81.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro30.301s (-92.8% 🟢)32.393s (-92.4% 🟢)2.092s31.00x
▲ VercelNext.js (Turbopack)34.399s (-91.3% 🟢)36.050s (-90.9% 🟢)1.651s31.14x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.156s (-22.3% 🟢)2.005s (~)0.849s151.00x
🐘 PostgresNitro1.159s (-9.1% 🟢)2.008s (~)0.849s151.00x
🐘 PostgresExpress1.160s (-8.0% 🟢)2.008s (~)0.848s151.00x
💻 LocalNitro1.180s (-27.6% 🟢)2.006s (-3.3%)0.825s151.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.412s (-14.4% 🟢)3.982s (-7.9% 🟢)1.570s81.00x
▲ VercelNext.js (Turbopack)4.906s (+44.4% 🔺)6.610s (+34.0% 🔺)1.704s52.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.228s (-48.0% 🟢)2.008s (-33.3% 🟢)0.780s151.00x
🐘 PostgresNitro1.238s (-47.3% 🟢)2.006s (-33.3% 🟢)0.768s151.01x
💻 LocalExpress1.598s (-45.9% 🟢)2.005s (-41.9% 🟢)0.407s151.30x
💻 LocalNitro1.894s (-39.8% 🟢)2.149s (-44.7% 🟢)0.256s141.54x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.300s (-18.6% 🟢)5.165s (-12.8% 🟢)1.865s61.00x
▲ VercelNext.js (Turbopack)5.590s (-21.3% 🟢)7.240s (-18.7% 🟢)1.651s51.69x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.380s (-60.4% 🟢)2.009s (-49.9% 🟢)0.629s151.00x
🐘 PostgresNitro1.390s (-60.0% 🟢)2.008s (-49.9% 🟢)0.617s151.01x
💻 LocalExpress4.503s (-46.0% 🟢)4.725s (-47.7% 🟢)0.222s73.26x
💻 LocalNitro5.496s (-34.2% 🟢)6.014s (-33.3% 🟢)0.518s53.98x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)5.990s (-32.8% 🟢)7.487s (-31.7% 🟢)1.496s51.00x
▲ VercelNitro6.899s (+95.7% 🔺)10.766s (+94.6% 🔺)3.867s31.15x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.159s (-7.8% 🟢)2.009s (~)0.850s151.00x
🐘 PostgresNitro1.165s (-7.4% 🟢)2.009s (~)0.844s151.00x
💻 LocalExpress1.317s (-30.5% 🟢)2.005s (-15.2% 🟢)0.689s151.14x
💻 LocalNitro1.415s (-24.1% 🟢)2.007s (-14.3% 🟢)0.591s151.22x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.462s (~)3.978s (-4.6%)1.516s81.00x
▲ VercelNext.js (Turbopack)5.008s (+70.8% 🔺)6.602s (+42.2% 🔺)1.594s52.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.221s (-47.8% 🟢)2.009s (-33.3% 🟢)0.788s151.00x
🐘 PostgresExpress1.241s (-47.0% 🟢)2.008s (-33.3% 🟢)0.767s151.02x
💻 LocalExpress1.666s (-46.8% 🟢)2.005s (-46.7% 🟢)0.339s151.36x
💻 LocalNitro2.096s (-31.6% 🟢)2.592s (-33.3% 🟢)0.496s121.72x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.255s (+0.7%)5.219s (+2.8%)1.965s61.00x
▲ VercelNext.js (Turbopack)4.456s (+41.8% 🔺)6.286s (+39.0% 🔺)1.830s51.37x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.358s (-61.0% 🟢)2.007s (-49.9% 🟢)0.649s151.00x
🐘 PostgresExpress1.379s (-60.6% 🟢)2.008s (-49.9% 🟢)0.628s151.02x
💻 LocalExpress4.938s (-43.9% 🟢)5.513s (-40.6% 🟢)0.575s63.64x
💻 LocalNitro6.426s (-29.7% 🟢)6.819s (-32.0% 🟢)0.393s54.73x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.698s (-7.8% 🟢)6.786s (~)2.088s51.00x
▲ VercelNext.js (Turbopack)5.481s (-18.9% 🟢)7.171s (-16.1% 🟢)1.689s51.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.429s (-47.8% 🟢)1.006s (~)0.578s601.00x
🐘 PostgresExpress0.435s (-48.2% 🟢)1.007s (-1.6%)0.572s601.01x
💻 LocalExpress0.444s (-54.8% 🟢)1.004s (-6.7% 🟢)0.559s601.04x
💻 LocalNitro0.471s (-52.0% 🟢)1.004s (-8.2% 🟢)0.534s601.10x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.925s (-77.7% 🟢)7.020s (-70.8% 🟢)2.095s91.00x
▲ VercelNext.js (Turbopack)6.772s (-53.3% 🟢)8.207s (-49.0% 🟢)1.435s81.37x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.028s (-46.7% 🟢)1.413s (-32.7% 🟢)0.384s641.00x
🐘 PostgresExpress1.065s (-46.1% 🟢)1.771s (-21.5% 🟢)0.706s511.04x
💻 LocalExpress1.125s (-62.7% 🟢)2.005s (-44.1% 🟢)0.880s451.09x
💻 LocalNitro1.184s (-61.0% 🟢)2.006s (-46.6% 🟢)0.822s451.15x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro12.382s (-68.6% 🟢)14.665s (-64.5% 🟢)2.283s71.00x
▲ VercelNext.js (Turbopack)15.639s (-68.6% 🟢)17.695s (-65.8% 🟢)2.056s61.26x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.974s (-51.9% 🟢)2.380s (-48.3% 🟢)0.405s511.00x
🐘 PostgresExpress2.076s (-48.0% 🟢)2.529s (-42.1% 🟢)0.453s481.05x
💻 LocalExpress2.545s (-72.4% 🟢)3.032s (-69.7% 🟢)0.488s401.29x
💻 LocalNitro2.664s (-71.4% 🟢)3.057s (-69.5% 🟢)0.394s401.35x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro38.936s (-59.8% 🟢)41.471s (-57.9% 🟢)2.535s41.00x
▲ VercelNext.js (Turbopack)45.359s (-57.7% 🟢)47.056s (-56.8% 🟢)1.697s31.16x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.186s (-34.3% 🟢)1.005s (~)0.819s601.00x
🐘 PostgresExpress0.200s (-29.0% 🟢)1.006s (~)0.806s601.08x
💻 LocalExpress0.389s (-30.6% 🟢)1.004s (~)0.615s602.09x
💻 LocalNitro0.441s (-27.1% 🟢)1.004s (-1.7%)0.563s602.37x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.370s (+42.7% 🔺)3.938s (+17.5% 🔺)1.568s161.00x
▲ VercelNext.js (Turbopack)3.739s (+84.9% 🔺)5.211s (+37.4% 🔺)1.473s121.58x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.307s (-38.1% 🟢)1.006s (~)0.699s901.00x
🐘 PostgresExpress0.329s (-35.5% 🟢)1.006s (~)0.678s901.07x
💻 LocalExpress2.111s (-16.0% 🟢)2.684s (-10.8% 🟢)0.573s346.88x
💻 LocalNitro2.201s (-13.3% 🟢)2.945s (-2.1%)0.744s317.17x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.219s (+123.8% 🔺)9.007s (+86.8% 🔺)1.788s101.00x
▲ VercelNext.js (Turbopack)7.230s (+104.5% 🔺)8.888s (+71.1% 🔺)1.657s111.00x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.623s (-21.2% 🟢)1.006s (~)0.383s1201.00x
🐘 PostgresExpress0.676s (-17.4% 🟢)1.007s (-1.1%)0.330s1201.09x
💻 LocalExpress8.906s (-20.4% 🟢)9.485s (-20.6% 🟢)0.579s1314.30x
💻 LocalNitro10.253s (-8.4% 🟢)10.694s (-8.3% 🟢)0.441s1216.47x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)22.466s (+117.5% 🔺)23.969s (+95.1% 🔺)1.504s61.00x
▲ VercelNitro24.505s (+217.3% 🔺)26.705s (+184.1% 🔺)2.200s51.09x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.120s (+462.6% 🔺)2.004s (+99.5% 🔺)0.009s (-22.3% 🟢)2.016s (+98.0% 🔺)0.896s101.00x
💻 LocalNitro1.134s (+430.7% 🔺)2.006s (+99.7% 🔺)0.012s (-0.8%)2.020s (+98.3% 🔺)0.886s101.01x
🐘 PostgresNitro1.140s (+456.2% 🔺)2.002s (+100.3% 🔺)0.001s (-13.3% 🟢)2.011s (+98.8% 🔺)0.871s101.02x
🐘 PostgresExpress1.159s (+465.2% 🔺)1.998s (+100.1% 🔺)0.001s (-12.5% 🟢)2.011s (+98.8% 🔺)0.852s101.03x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.439s (-36.4% 🟢)3.535s (-33.0% 🟢)2.339s (+215.2% 🔺)6.477s (~)4.038s101.00x
▲ VercelNext.js (Turbopack)4.213s (-38.5% 🟢)4.295s (-50.3% 🟢)1.310s (+107.3% 🔺)7.294s (-25.5% 🟢)3.081s101.73x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.488s (+138.4% 🔺)2.003s (+99.0% 🔺)0.004s (-3.3%)2.025s (+98.0% 🔺)0.536s301.00x
💻 LocalNitro1.523s (+81.6% 🔺)2.012s (+98.8% 🔺)0.010s (+5.3% 🔺)2.023s (+81.3% 🔺)0.500s301.02x
🐘 PostgresExpress1.540s (+144.4% 🔺)2.009s (+99.6% 🔺)0.004s (+3.6%)2.026s (+98.0% 🔺)0.486s301.03x
💻 LocalExpress1.665s (+119.9% 🔺)2.009s (+95.2% 🔺)0.009s (-1.0%)2.199s (+111.5% 🔺)0.534s281.12x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.887s (-80.0% 🟢)7.416s (-75.9% 🟢)0.184s (+64.0% 🔺)8.100s (-74.5% 🟢)2.213s81.00x
▲ VercelNext.js (Turbopack)13.623s (-19.5% 🟢)13.512s (-25.9% 🟢)0.199s (-5.7% 🟢)15.368s (-18.8% 🟢)1.744s42.31x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.653s (-32.6% 🟢)1.050s (-15.9% 🟢)0.000s (-57.9% 🟢)1.060s (-15.7% 🟢)0.407s571.00x
🐘 PostgresExpress0.672s (-30.0% 🟢)1.033s (-19.2% 🟢)0.000s (+19.0% 🔺)1.042s (-20.2% 🟢)0.370s581.03x
💻 LocalExpress1.274s (+4.0%)2.013s (~)0.000s (-40.0% 🟢)2.015s (~)0.740s301.95x
💻 LocalNitro1.310s (+7.1% 🔺)2.017s (~)0.001s (+400.0% 🔺)2.019s (~)0.709s302.01x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.313s (+8.6% 🔺)4.592s (+4.5%)0.000s (+8.3% 🔺)5.023s (+4.5%)1.710s121.00x
▲ VercelNext.js (Turbopack)5.512s (-45.9% 🟢)6.062s (-47.4% 🟢)0.000s (+Infinity% 🔺)6.982s (-42.1% 🟢)1.470s91.66x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.168s (-34.8% 🟢)1.996s (-6.8% 🟢)0.000s (-100.0% 🟢)2.026s (-6.8% 🟢)0.858s301.00x
🐘 PostgresExpress1.377s (-22.3% 🟢)2.066s (-5.1% 🟢)0.000s (NaN%)2.082s (-5.3% 🟢)0.705s291.18x
💻 LocalExpress2.968s (-14.4% 🟢)3.555s (-11.9% 🟢)0.000s (-70.6% 🟢)3.559s (-11.8% 🟢)0.591s172.54x
💻 LocalNitro3.084s (-9.0% 🟢)3.781s (-6.2% 🟢)0.000s (-29.7% 🟢)3.783s (-6.3% 🟢)0.700s162.64x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.124s (+25.2% 🔺)6.356s (+18.3% 🔺)0.000s (-100.0% 🟢)6.813s (+17.6% 🔺)1.689s91.00x
▲ VercelNext.js (Turbopack)7.535s (+34.2% 🔺)8.909s (+27.6% 🔺)0.000s (-100.0% 🟢)9.344s (+23.9% 🔺)1.809s71.47x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress18/21
🐘 PostgresNitro17/21
▲ VercelNitro19/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

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

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actionsBot commented May 4, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production92502191144
✅ 💻 Local Development123702191456
✅ 📦 Local Production123702191456
✅ 🐘 Local Postgres123702191456
✅ 🪟 Windows10400104
✅ 📋 Other5520176728
Total5292010526344

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro78026
✅ example78026
✅ express78026
✅ fastify78026
✅ hono78026
✅ nextjs-turbopack10202
✅ nextjs-webpack10202
✅ nitro78026
✅ nuxt78026
✅ sveltekit9707
✅ vite78026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack10400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable79025
✅ e2e-local-dev-tanstack-start-79025
✅ e2e-local-postgres-nest-stable79025
✅ e2e-local-postgres-tanstack-start-79025
✅ e2e-local-prod-nest-stable79025
✅ e2e-local-prod-tanstack-start-79025
✅ e2e-vercel-prod-tanstack-start78026

📋 View full workflow run

Adds allowedDevOrigins entries so portless-style worktree-prefixed
.localhost URLs (e.g. https://<branch>.turbopack.localhost) can hit
HMR and dev-only endpoints without Next's cross-origin protection
flooding the logs with warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

CopilotAI 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.

Pull request overview

Adds new end-to-end coverage for Promise.race behavior between a long-running step and sleep(), and introduces a new dev-tmux skill to standardize a local 3-pane dev setup (workbench + observability UI + scratchpad) using portless for worktree-isolated .localhost URLs.

Changes:

  • Add sleepWinsRaceWorkflow / stepWinsRaceWorkflow workflows to the workbench e2e workflow collection.
  • Add corresponding Vitest e2e tests asserting the expected race winner and duration bounds.
  • Add skills/dev-tmux/SKILL.md documentation for a tmux + portless local dev workflow; add a (currently empty) changeset file.

Reviewed changes

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

FileDescription
workbench/example/workflows/99_e2e.tsAdds two new workflows plus a shared step helper to exercise Promise.race between a step and sleep().
packages/core/e2e/e2e.test.tsAdds two e2e tests covering the new race workflows and asserting winner + duration.
skills/dev-tmux/SKILL.mdNew skill documenting a portless-routed 3-pane tmux setup for local SDK dev + e2e runs.
.changeset/better-pets-reply.mdAdds a new changeset file, but it is currently empty/invalid.

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

Comment on lines +584 to +590
test('sleepWinsRaceWorkflow', { timeout: 60_000 }, async () => {
const run = await start(await e2e('sleepWinsRaceWorkflow'), []);
const returnValue = await run.returnValue;
expect(returnValue.winner).toBe('sleep');
// Sleep is 1s; step would take 10s. Should resolve in ~1s, well under 5s.
expect(returnValue.durationMs).toBeLessThan(5_000);
});

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Obsolete after rebase onto main — PR #1924 fixed the underlying race semantics and added these same tests directly to main. The duplicate set on this branch was dropped (031e4b8); the race tests are no longer part of this PR's diff.

Comment thread.changeset/better-pets-reply.md
Comment threadskills/dev-tmux/SKILL.md Outdated
Comment on lines +51 to +59
tmux send-keys -t "$SESSION".1 \
'cd workbench/nextjs-turbopack && WORKFLOW_PUBLIC_MANIFEST=1 portless run --name turbopack pnpm dev' C-m

# Pane 2 (top-right): observability UI scoped to the workbench app
tmux send-keys -t "$SESSION".2 \
'cd workbench/nextjs-turbopack && portless run --name workflow-obs sh -c "pnpm workflow web --webPort \$PORT --noBrowser"' C-m

# Pane 3 (bottom-right): scratchpad at repo root
tmux send-keys -t "$SESSION".3 'echo "scratchpad: $(pwd)"' C-m

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Fixed in 5a44360. The skill now captures each pane's ID at split time with -P -F '#{pane_id}' and uses those IDs as targets, so it's correct under both pane-base-index 0 and pane-base-index 1 regardless of the user's tmux config.

Comment threadpackages/core/e2e/e2e.test.ts
TooTallNate
TooTallNate previously requested changes May 4, 2026

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verdict: Request Changes

The PR has two things in it: e2e race tests (good idea, surfacing a real V2-handler semantics question) and a personal-dev skill file. I have one blocker on the test, one scope concern on the skill, and one minor nit on the next.config change.


Summary of inline comments

  1. packages/core/e2e/e2e.test.ts:587 — Blocker.sleepWinsRaceWorkflow fails on every e2e job (56+ jobs across all frameworks × all environments). Needs test.fails(...), .skip with TODO, or removal until the underlying race semantics are decided. The PR description correctly identifies this as intentional pending discussion, but the current state makes the e2e matrix unusable for any PR based on this one.

  2. skills/dev-tmux/SKILL.md:3 — Scope. Activation phrases ("spin up the dev session", "start dev mode", "set up the workbench") will trigger for general contributor questions, then push them toward installing portless. Either narrow the phrases to portless-specific terms or move the skill to a user-level location.

  3. workbench/nextjs-turbopack/next.config.ts:13 — Nit. Hardcoding *.turbopack.localhost in the workbench bakes one developer's convention into the shared config. Dev-only, so harmless — flagging for awareness, not blocking.


What I verified

  • Diagnosis is correct: The V2 inline-execution model in workflowEntrypoint (packages/core/src/runtime.ts) executes step bodies inline rather than enqueueing them. A 10-second delayMsStep blocks the handler for 10 seconds, which prevents the 1-second sleep('1s') timer from firing on time. So Promise.race ends up resolving with 'step' because that's what the handler delivers first. Whether this should be considered correct behavior or a bug is a real question.
  • Symlinks: Confirmed 99_e2e.ts is symlinked from workbench/example/workflows/99_e2e.ts into all other workbenches per AGENTS.md convention. ✅
  • Empty changeset: ---\n--- is the right pattern for tests-only PRs that don't ship package changes; matches existing examples (fix-vite-step-source-maps-e2e.md, tanstack-start-workbench.md). ✅
  • Skill frontmatter: version: '1.0' is fine for a new skill (existing migrating-to-workflow-sdk is '0.2.0'). Format matches.
  • Typecheck: Clean across all 40 packages.

Suggested path forward

Smallest change that keeps the value of this PR:

  1. Mark sleepWinsRaceWorkflow with test.fails(...) and add a code comment linking to an issue tracking the V2 race-semantics decision. That preserves the test as a regression detector — if the underlying behavior is ever fixed, the test will go red and force a follow-up.
  2. Optional: open a separate small skill PR after clarifying scope, or move the skill to a personal location.
  3. Land stepWinsRaceWorkflow as a regular passing test — it's a useful regression check on the easy direction.

Happy to re-review once the failing assertion is addressed.

test('sleepWinsRaceWorkflow', { timeout: 60_000 }, async () => {
const run = await start(await e2e('sleepWinsRaceWorkflow'), []);
const returnValue = await run.returnValue;
expect(returnValue.winner).toBe('sleep');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocker — every e2e CI job is failing because of this assertion.

This test fails on every framework's e2e run (express, hono, nitro, fastify, astro, vite, nuxt, sveltekit, tanstack-start, both Next.js variants — local-dev, local-prod, local-postgres, AND vercel-prod) because 99_e2e.ts is shared across all workbenches via symlink. As of the latest run, that's 56+ failed jobs all on expected 'step' to be 'sleep'.

For example: E2E Local Dev Tests (express - stable)FAIL packages/core/e2e/e2e.test.ts > e2e > sleepWinsRaceWorkflow / AssertionError: expected 'step' to be 'sleep'.

The PR description acknowledges this is intentional and wants discussion before merge. That's reasonable as a discussion vehicle, but it can't merge in this state — the PR effectively makes the entire e2e matrix red for any other PR that bases on this one.

Until the underlying behavior question is settled, please pick one:

  1. test.fails(...) — vitest will pass when the assertion fails, and flip to red if/when the runtime is fixed. Cleanly documents the known-broken behavior.
  2. test.skip(...) with a TODO comment linking to the discussion / issue tracking the race semantics decision.
  3. Drop the failing test from this PR and land just stepWinsRaceWorkflow (which is the meaningful regression-prevention test); open a follow-up issue or PR for the slow-step-vs-fast-sleep case once the semantics are decided.

For reference, the underlying cause is consistent with the V2 inline-execution model in packages/core/src/runtime.ts (workflowEntrypoint): the handler executes the step inline rather than enqueuing it, so a 10s step blocks the handler for 10s and prevents the 1s sleep timer from firing on time. That's a real semantics question worth resolving — but in a separate change.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Resolved — merged latest main, which carried PR #1924's fix for the inline-execution behavior and added these same race tests directly. The duplicate set landed by this branch was dropped (commit 031e4b8), so 99_e2e.ts now matches main exactly and the race tests are no longer part of this PR's diff. Both sleepWinsRaceWorkflow and stepWinsRaceWorkflow now run from main's definitions and pass. This PR is dev-tooling only after the rebase.

Comment threadskills/dev-tmux/SKILL.md Outdated
@@ -0,0 +1,125 @@
---
name: dev-tmux
description: Spin up a 3-pane tmux session for local Workflow SDK development — Next.js turbopack workbench, observability UI, and a scratchpad — all routed through portless so each worktree gets isolated `.localhost` URLs. Use when the user asks to "spin up the dev session", "start dev mode", "set up the workbench", or any time they want to test workflows interactively in a worktree alongside the observability UI.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Activation phrases here are very generic — "spin up the dev session", "start dev mode", "set up the workbench" all describe the general task most contributors do daily, not the specific portless+tmux setup this skill describes. As written, this skill will activate for any contributor asking how to start the dev server, then drive them toward installing portless and adopting a personal tmux layout that the rest of the team doesn't use.

A couple of options:

  1. Narrow the activation phrases to portless-specific terms: "portless dev session", "tmux dev layout", "worktree-isolated dev URLs", etc. That way the skill only fires for users who already know they want this specific tooling.
  2. Move it out of the shared skills/ directory. This appears to be one developer's personal workflow tooling rather than a project-wide convention — skills/migrating-to-workflow-sdk/ is documenting a user-facing capability, whereas this is documenting an internal contributor's local-dev preference. A user-level agent skill (e.g. ~/.agents/skills/dev-tmux/) would keep it personal without affecting other contributors.

(The skill itself is well-written and the troubleshooting section is genuinely useful — this is purely about scope/activation, not quality.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Tightened in 5a44360 (v1.1 of the skill). The description now opens with Spin up a portless + tmux dev session ... and explicitly lists the only triggers as portless dev session / tmux dev layout for workflow / worktree-isolated dev URLs / wanting to wire URLs into the Claude statusline. It also explicitly says not to activate for the generic start the dev server / run pnpm dev task. Should now stay out of any contributor's path who isn't already opting into this tooling.

Kept it under skills/ rather than moving to ~/.claude/ because v1.1 also adds a statusline.sh helper script that needs to live next to the skill — moving it out would split the two artifacts. Open to revisiting if option 2 still feels right.

Comment threadworkbench/nextjs-turbopack/next.config.ts
Worktrees get deleted, so wiring the statusline to a worktree path
breaks the moment the worktree is removed. Update the skill and the
script header to recommend pointing `statusLine.command` at the
primary checkout (`$HOME/github/vercel/workflow/...`). The script
itself is already worktree-aware via Claude's `workspace.current_dir`
stdin JSON, so the same invocation surfaces routes for whichever
worktree the session is in.
Bump version to 1.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment threadskills/dev-tmux/SKILL.md Outdated
Comment on lines +5 to +6
author: Vercel Inc.
version: '1.2'

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

change the author to "Pranay Prakash" and set the version to 0.1 for this first release (since it's a new skill anyway)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 8b7225bmetadata.author: Pranay Prakash, metadata.version: '0.1'.

Comment threadskills/dev-tmux/SKILL.md Outdated
@@ -0,0 +1,160 @@
---
name: dev-tmux

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Suggested change
name: dev-tmux
name: internal-dev-workbench

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Applied in 8b7225bgit mv skills/dev-tmux skills/internal-dev-workbench, plus the matching frontmatter name: change and updates to all internal references in SKILL.md and statusline.sh.

@pranaygp

Copy link
Copy Markdown
ContributorAuthor

making some changes to the skill and testing it a bit more. moving to draft for now

pranaygpand others added 4 commits May 5, 2026 10:50
- Statusline overlay now renders `[dev] · [obs] · tmux:<prefix>`,
with the bracketed labels emitted as OSC 8 hyperlinks (clickable in
any modern terminal) styled cyan + underline so they stand out.
Replaces the old long-URL form that was hard to scan and click.
- Add a tmux-session indicator: shown when a session named exactly the
worktree prefix exists (uses `tmux has-session -t =<prefix>` for
exact matching).
- Change the skill's tmux session naming convention from the fixed
`workflow-dev` to `<worktree-prefix>` (basename of the branch — same
string portless uses as the subdomain prefix). This lets the
statusline locate the session deterministically and lets multiple
worktrees run dev sessions concurrently without manual disambiguation.
- Bump skill to v1.3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the abbreviated `tmux:<prefix>` indicator with the full
copy-paste-ready `tmux attach -t <prefix>` invocation. Saves a step
when grabbing the session from another shell.
Bump skill to v1.4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop the dim styling that made the overlay hard to read; use bold
bright cyan + underline for links and bold bright green for the
tmux command.
- Add Nerd Font glyphs: for dev, for obs, for the
tmux copy-paste hint. Falls back to box-drawing if the font lacks
Nerd Font ranges; layout is unaffected.
- Visual differentiation: cyan + underline = clickable hyperlink;
green = copy this command.
Bump skill to v1.5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The copy glyph in `emit_tmux` was a literal Nerd Font byte embedded in
the printf string and got stripped during a prior rewrite. Promote all
three icons (rocket / graph / copy) to top-level shell variables that
use \uHHHH-equivalent UTF-8 escapes, so the source survives editor
round-trips that don't preserve Private Use Area code points.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pranaygp

Copy link
Copy Markdown
ContributorAuthor

Updates since the previous round of review replies — all in skills/dev-tmux/:

Statusline overhaul (commits c714162938c26e)

  • The statusline command should be wired against the primary checkout ($HOME/github/vercel/workflow/skills/dev-tmux/statusline.sh), not a worktree path that disappears on cleanup. The script remains worktree-aware via Claude's workspace.current_dir stdin JSON, so a single stable path correctly surfaces routes for whichever worktree the session is in.
  • Output format reworked from long URLs to compact OSC 8 hyperlinks: [rocket] dev · [graph] obs · [copy] tmux attach -t <prefix>. The bracketed parts are clickable in iTerm2 / Kitty / WezTerm / Terminal.app / Ghostty (cmd/ctrl-click).
  • Bold + bright styling — bold bright cyan + underline for the clickable links, bold bright green for the tmux attach command (visually distinguishes "click this" from "copy this"). Earlier dim styling was hard to read.
  • Nerd Font icons (octicon rocket, octicon graph, fa copy) defined as top-level shell variables using UTF-8 escapes so they survive editor round-trips that don't preserve PUA code points.

Tmux session naming convention (commit 431026c)

  • The skill now creates the session with <worktree-prefix> (basename of the branch — same string portless uses for the <prefix>.<name>.localhost subdomain) instead of the fixed workflow-dev. Lets multiple worktrees run dev sessions concurrently with no manual disambiguation, and lets the statusline locate the session deterministically via tmux has-session -t =<prefix>.
  • The statusline shows the full tmux attach -t <prefix> invocation (commit b0f373c) so it's directly copy-pasteable into another shell.

Skill version bumped to 1.5. Marking ready for review.

…set version
- Rename `skills/dev-tmux/` → `skills/internal-dev-workbench/` to make
the name self-explanatory about the skill's scope (an internal
contributor's local dev workbench, not a generic tmux helper).
- Author: Pranay Prakash. Version: 0.1 (first release of the skill).
- Update internal references in SKILL.md and statusline.sh accordingly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

4 participants

@pranaygp@TooTallNate@VaguelySerious
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add dev-tmux skill for portless+tmux local Workflow SDK dev - #1916

Merged
pranaygp merged 11 commits into
mainfrom
pgp/stepflow-test
May 5, 2026
Merged

Add dev-tmux skill for portless+tmux local Workflow SDK dev#1916
pranaygp merged 11 commits into
mainfrom
pgp/stepflow-test

Conversation

@pranaygp

@pranaygppranaygp commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • New dev-tmux skill documenting the 3-pane tmux + portless setup for local Workflow SDK development (turbopack workbench, observability UI scoped to that workbench, scratchpad shell — all worktree-isolated via portless's branch-prefixed .localhost URLs).
  • Adds allowedDevOrigins: ['turbopack.localhost', '*.turbopack.localhost'] to workbench/nextjs-turbopack/next.config.ts so portless-style URLs don't get blocked by Next's cross-origin protection in dev (currently floods the logs with HMR warnings).

What changed since the original PR

The original PR also added two race workflows (sleepWinsRaceWorkflow, stepWinsRaceWorkflow) and their e2e tests to surface a Promise.race-vs-replay semantics issue. Those landed independently in #1924 (with the underlying fix), so this branch was rebased onto main and the duplicate additions removed — the race tests now come from main. This PR is dev-tooling only.

Test plan

  • pnpm vitest run packages/core/e2e/e2e.test.ts -t WinsRaceWorkflow against the turbopack workbench (passes after rebase, since [core] V2: skip inline step execution when suspension also has a wait #1924's fix is on main)
  • Spin up the workbench by following skills/dev-tmux/SKILL.md and confirm both https://<branch>.turbopack.localhost (HMR works, no cross-origin warnings) and https://<branch>.workflow-obs.localhost (observability UI shows runs from the workbench)

🤖 Generated with Claude Code

Adds two race workflows (sleepWinsRaceWorkflow, stepWinsRaceWorkflow)
that exercise Promise.race between a step function and a sleep call.
The current `sleepWinsRaceWorkflow` test fails — surfacing how the
replay engine resolves a previously-completed step instantly while
sleep still has to elapse.
Also adds a `dev-tmux` skill that documents the 3-pane tmux + portless
setup for testing workflows interactively in a worktree alongside the
observability UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings May 4, 2026 11:48
@changeset-bot

changeset-botBot commented May 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8b7225b

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 May 4, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 4, 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🥇 Nitro0.031s (-27.8% 🟢)1.005s (~)0.974s101.00x
💻 LocalExpress0.034s (-22.3% 🟢)1.005s (~)0.970s101.11x
🐘 PostgresExpress0.046s (-20.7% 🟢)1.012s (~)0.966s101.48x
🐘 PostgresNitro0.046s (-51.7% 🟢)1.012s (-3.0%)0.966s101.48x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.315s (-23.1% 🟢)2.072s (-17.4% 🟢)1.758s101.00x
▲ VercelNext.js (Turbopack)0.793s (+215.3% 🔺)2.620s (+12.3% 🔺)1.827s102.52x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.068s (-5.1% 🟢)2.006s (~)0.938s101.00x
💻 LocalNitro1.073s (-5.2% 🟢)2.006s (~)0.933s101.00x
🐘 PostgresNitro1.083s (-5.0%)2.011s (~)0.927s101.01x
🐘 PostgresExpress1.085s (-5.4% 🟢)2.010s (~)0.925s101.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.777s (-54.3% 🟢)3.967s (-32.9% 🟢)2.190s101.00x
▲ VercelNext.js (Turbopack)2.267s (+11.4% 🔺)3.754s (-2.0%)1.487s101.28x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.402s (-4.8%)11.020s (~)0.618s31.00x
🐘 PostgresNitro10.402s (-4.3%)11.017s (~)0.615s31.00x
💻 LocalNitro10.414s (-4.9%)11.022s (~)0.607s31.00x
🐘 PostgresExpress10.446s (-4.7%)11.019s (~)0.574s31.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.463s (-43.3% 🟢)15.032s (-40.2% 🟢)1.568s21.00x
▲ VercelNext.js (Turbopack)14.098s (-18.6% 🟢)15.843s (-18.3% 🟢)1.745s21.05x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.436s (-10.2% 🟢)14.025s (-6.7% 🟢)0.589s51.00x
🐘 PostgresNitro13.445s (-7.9% 🟢)14.018s (-6.7% 🟢)0.573s51.00x
💻 LocalNitro13.481s (-10.5% 🟢)14.027s (-12.5% 🟢)0.546s51.00x
🐘 PostgresExpress13.493s (-7.5% 🟢)14.024s (-6.6% 🟢)0.531s51.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro21.085s (-67.3% 🟢)23.044s (-65.4% 🟢)1.959s31.00x
▲ VercelNext.js (Turbopack)22.401s (-57.4% 🟢)24.203s (-55.7% 🟢)1.802s31.06x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro11.880s (-14.9% 🟢)12.016s (-16.0% 🟢)0.136s81.00x
💻 LocalNitro11.939s (-28.9% 🟢)12.147s (-28.7% 🟢)0.208s81.00x
💻 LocalExpress11.974s (-27.9% 🟢)12.396s (-27.2% 🟢)0.422s81.01x
🐘 PostgresExpress12.130s (-13.4% 🟢)12.642s (-13.4% 🟢)0.512s81.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro30.301s (-92.8% 🟢)32.393s (-92.4% 🟢)2.092s31.00x
▲ VercelNext.js (Turbopack)34.399s (-91.3% 🟢)36.050s (-90.9% 🟢)1.651s31.14x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.156s (-22.3% 🟢)2.005s (~)0.849s151.00x
🐘 PostgresNitro1.159s (-9.1% 🟢)2.008s (~)0.849s151.00x
🐘 PostgresExpress1.160s (-8.0% 🟢)2.008s (~)0.848s151.00x
💻 LocalNitro1.180s (-27.6% 🟢)2.006s (-3.3%)0.825s151.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.412s (-14.4% 🟢)3.982s (-7.9% 🟢)1.570s81.00x
▲ VercelNext.js (Turbopack)4.906s (+44.4% 🔺)6.610s (+34.0% 🔺)1.704s52.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.228s (-48.0% 🟢)2.008s (-33.3% 🟢)0.780s151.00x
🐘 PostgresNitro1.238s (-47.3% 🟢)2.006s (-33.3% 🟢)0.768s151.01x
💻 LocalExpress1.598s (-45.9% 🟢)2.005s (-41.9% 🟢)0.407s151.30x
💻 LocalNitro1.894s (-39.8% 🟢)2.149s (-44.7% 🟢)0.256s141.54x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.300s (-18.6% 🟢)5.165s (-12.8% 🟢)1.865s61.00x
▲ VercelNext.js (Turbopack)5.590s (-21.3% 🟢)7.240s (-18.7% 🟢)1.651s51.69x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.380s (-60.4% 🟢)2.009s (-49.9% 🟢)0.629s151.00x
🐘 PostgresNitro1.390s (-60.0% 🟢)2.008s (-49.9% 🟢)0.617s151.01x
💻 LocalExpress4.503s (-46.0% 🟢)4.725s (-47.7% 🟢)0.222s73.26x
💻 LocalNitro5.496s (-34.2% 🟢)6.014s (-33.3% 🟢)0.518s53.98x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)5.990s (-32.8% 🟢)7.487s (-31.7% 🟢)1.496s51.00x
▲ VercelNitro6.899s (+95.7% 🔺)10.766s (+94.6% 🔺)3.867s31.15x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.159s (-7.8% 🟢)2.009s (~)0.850s151.00x
🐘 PostgresNitro1.165s (-7.4% 🟢)2.009s (~)0.844s151.00x
💻 LocalExpress1.317s (-30.5% 🟢)2.005s (-15.2% 🟢)0.689s151.14x
💻 LocalNitro1.415s (-24.1% 🟢)2.007s (-14.3% 🟢)0.591s151.22x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.462s (~)3.978s (-4.6%)1.516s81.00x
▲ VercelNext.js (Turbopack)5.008s (+70.8% 🔺)6.602s (+42.2% 🔺)1.594s52.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.221s (-47.8% 🟢)2.009s (-33.3% 🟢)0.788s151.00x
🐘 PostgresExpress1.241s (-47.0% 🟢)2.008s (-33.3% 🟢)0.767s151.02x
💻 LocalExpress1.666s (-46.8% 🟢)2.005s (-46.7% 🟢)0.339s151.36x
💻 LocalNitro2.096s (-31.6% 🟢)2.592s (-33.3% 🟢)0.496s121.72x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.255s (+0.7%)5.219s (+2.8%)1.965s61.00x
▲ VercelNext.js (Turbopack)4.456s (+41.8% 🔺)6.286s (+39.0% 🔺)1.830s51.37x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.358s (-61.0% 🟢)2.007s (-49.9% 🟢)0.649s151.00x
🐘 PostgresExpress1.379s (-60.6% 🟢)2.008s (-49.9% 🟢)0.628s151.02x
💻 LocalExpress4.938s (-43.9% 🟢)5.513s (-40.6% 🟢)0.575s63.64x
💻 LocalNitro6.426s (-29.7% 🟢)6.819s (-32.0% 🟢)0.393s54.73x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.698s (-7.8% 🟢)6.786s (~)2.088s51.00x
▲ VercelNext.js (Turbopack)5.481s (-18.9% 🟢)7.171s (-16.1% 🟢)1.689s51.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.429s (-47.8% 🟢)1.006s (~)0.578s601.00x
🐘 PostgresExpress0.435s (-48.2% 🟢)1.007s (-1.6%)0.572s601.01x
💻 LocalExpress0.444s (-54.8% 🟢)1.004s (-6.7% 🟢)0.559s601.04x
💻 LocalNitro0.471s (-52.0% 🟢)1.004s (-8.2% 🟢)0.534s601.10x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.925s (-77.7% 🟢)7.020s (-70.8% 🟢)2.095s91.00x
▲ VercelNext.js (Turbopack)6.772s (-53.3% 🟢)8.207s (-49.0% 🟢)1.435s81.37x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.028s (-46.7% 🟢)1.413s (-32.7% 🟢)0.384s641.00x
🐘 PostgresExpress1.065s (-46.1% 🟢)1.771s (-21.5% 🟢)0.706s511.04x
💻 LocalExpress1.125s (-62.7% 🟢)2.005s (-44.1% 🟢)0.880s451.09x
💻 LocalNitro1.184s (-61.0% 🟢)2.006s (-46.6% 🟢)0.822s451.15x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro12.382s (-68.6% 🟢)14.665s (-64.5% 🟢)2.283s71.00x
▲ VercelNext.js (Turbopack)15.639s (-68.6% 🟢)17.695s (-65.8% 🟢)2.056s61.26x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.974s (-51.9% 🟢)2.380s (-48.3% 🟢)0.405s511.00x
🐘 PostgresExpress2.076s (-48.0% 🟢)2.529s (-42.1% 🟢)0.453s481.05x
💻 LocalExpress2.545s (-72.4% 🟢)3.032s (-69.7% 🟢)0.488s401.29x
💻 LocalNitro2.664s (-71.4% 🟢)3.057s (-69.5% 🟢)0.394s401.35x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro38.936s (-59.8% 🟢)41.471s (-57.9% 🟢)2.535s41.00x
▲ VercelNext.js (Turbopack)45.359s (-57.7% 🟢)47.056s (-56.8% 🟢)1.697s31.16x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.186s (-34.3% 🟢)1.005s (~)0.819s601.00x
🐘 PostgresExpress0.200s (-29.0% 🟢)1.006s (~)0.806s601.08x
💻 LocalExpress0.389s (-30.6% 🟢)1.004s (~)0.615s602.09x
💻 LocalNitro0.441s (-27.1% 🟢)1.004s (-1.7%)0.563s602.37x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.370s (+42.7% 🔺)3.938s (+17.5% 🔺)1.568s161.00x
▲ VercelNext.js (Turbopack)3.739s (+84.9% 🔺)5.211s (+37.4% 🔺)1.473s121.58x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.307s (-38.1% 🟢)1.006s (~)0.699s901.00x
🐘 PostgresExpress0.329s (-35.5% 🟢)1.006s (~)0.678s901.07x
💻 LocalExpress2.111s (-16.0% 🟢)2.684s (-10.8% 🟢)0.573s346.88x
💻 LocalNitro2.201s (-13.3% 🟢)2.945s (-2.1%)0.744s317.17x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.219s (+123.8% 🔺)9.007s (+86.8% 🔺)1.788s101.00x
▲ VercelNext.js (Turbopack)7.230s (+104.5% 🔺)8.888s (+71.1% 🔺)1.657s111.00x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.623s (-21.2% 🟢)1.006s (~)0.383s1201.00x
🐘 PostgresExpress0.676s (-17.4% 🟢)1.007s (-1.1%)0.330s1201.09x
💻 LocalExpress8.906s (-20.4% 🟢)9.485s (-20.6% 🟢)0.579s1314.30x
💻 LocalNitro10.253s (-8.4% 🟢)10.694s (-8.3% 🟢)0.441s1216.47x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)22.466s (+117.5% 🔺)23.969s (+95.1% 🔺)1.504s61.00x
▲ VercelNitro24.505s (+217.3% 🔺)26.705s (+184.1% 🔺)2.200s51.09x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.120s (+462.6% 🔺)2.004s (+99.5% 🔺)0.009s (-22.3% 🟢)2.016s (+98.0% 🔺)0.896s101.00x
💻 LocalNitro1.134s (+430.7% 🔺)2.006s (+99.7% 🔺)0.012s (-0.8%)2.020s (+98.3% 🔺)0.886s101.01x
🐘 PostgresNitro1.140s (+456.2% 🔺)2.002s (+100.3% 🔺)0.001s (-13.3% 🟢)2.011s (+98.8% 🔺)0.871s101.02x
🐘 PostgresExpress1.159s (+465.2% 🔺)1.998s (+100.1% 🔺)0.001s (-12.5% 🟢)2.011s (+98.8% 🔺)0.852s101.03x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.439s (-36.4% 🟢)3.535s (-33.0% 🟢)2.339s (+215.2% 🔺)6.477s (~)4.038s101.00x
▲ VercelNext.js (Turbopack)4.213s (-38.5% 🟢)4.295s (-50.3% 🟢)1.310s (+107.3% 🔺)7.294s (-25.5% 🟢)3.081s101.73x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.488s (+138.4% 🔺)2.003s (+99.0% 🔺)0.004s (-3.3%)2.025s (+98.0% 🔺)0.536s301.00x
💻 LocalNitro1.523s (+81.6% 🔺)2.012s (+98.8% 🔺)0.010s (+5.3% 🔺)2.023s (+81.3% 🔺)0.500s301.02x
🐘 PostgresExpress1.540s (+144.4% 🔺)2.009s (+99.6% 🔺)0.004s (+3.6%)2.026s (+98.0% 🔺)0.486s301.03x
💻 LocalExpress1.665s (+119.9% 🔺)2.009s (+95.2% 🔺)0.009s (-1.0%)2.199s (+111.5% 🔺)0.534s281.12x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.887s (-80.0% 🟢)7.416s (-75.9% 🟢)0.184s (+64.0% 🔺)8.100s (-74.5% 🟢)2.213s81.00x
▲ VercelNext.js (Turbopack)13.623s (-19.5% 🟢)13.512s (-25.9% 🟢)0.199s (-5.7% 🟢)15.368s (-18.8% 🟢)1.744s42.31x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.653s (-32.6% 🟢)1.050s (-15.9% 🟢)0.000s (-57.9% 🟢)1.060s (-15.7% 🟢)0.407s571.00x
🐘 PostgresExpress0.672s (-30.0% 🟢)1.033s (-19.2% 🟢)0.000s (+19.0% 🔺)1.042s (-20.2% 🟢)0.370s581.03x
💻 LocalExpress1.274s (+4.0%)2.013s (~)0.000s (-40.0% 🟢)2.015s (~)0.740s301.95x
💻 LocalNitro1.310s (+7.1% 🔺)2.017s (~)0.001s (+400.0% 🔺)2.019s (~)0.709s302.01x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.313s (+8.6% 🔺)4.592s (+4.5%)0.000s (+8.3% 🔺)5.023s (+4.5%)1.710s121.00x
▲ VercelNext.js (Turbopack)5.512s (-45.9% 🟢)6.062s (-47.4% 🟢)0.000s (+Infinity% 🔺)6.982s (-42.1% 🟢)1.470s91.66x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.168s (-34.8% 🟢)1.996s (-6.8% 🟢)0.000s (-100.0% 🟢)2.026s (-6.8% 🟢)0.858s301.00x
🐘 PostgresExpress1.377s (-22.3% 🟢)2.066s (-5.1% 🟢)0.000s (NaN%)2.082s (-5.3% 🟢)0.705s291.18x
💻 LocalExpress2.968s (-14.4% 🟢)3.555s (-11.9% 🟢)0.000s (-70.6% 🟢)3.559s (-11.8% 🟢)0.591s172.54x
💻 LocalNitro3.084s (-9.0% 🟢)3.781s (-6.2% 🟢)0.000s (-29.7% 🟢)3.783s (-6.3% 🟢)0.700s162.64x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.124s (+25.2% 🔺)6.356s (+18.3% 🔺)0.000s (-100.0% 🟢)6.813s (+17.6% 🔺)1.689s91.00x
▲ VercelNext.js (Turbopack)7.535s (+34.2% 🔺)8.909s (+27.6% 🔺)0.000s (-100.0% 🟢)9.344s (+23.9% 🔺)1.809s71.47x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress18/21
🐘 PostgresNitro17/21
▲ VercelNitro19/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

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

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actionsBot commented May 4, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production92502191144
✅ 💻 Local Development123702191456
✅ 📦 Local Production123702191456
✅ 🐘 Local Postgres123702191456
✅ 🪟 Windows10400104
✅ 📋 Other5520176728
Total5292010526344

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro78026
✅ example78026
✅ express78026
✅ fastify78026
✅ hono78026
✅ nextjs-turbopack10202
✅ nextjs-webpack10202
✅ nitro78026
✅ nuxt78026
✅ sveltekit9707
✅ vite78026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack10400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable79025
✅ e2e-local-dev-tanstack-start-79025
✅ e2e-local-postgres-nest-stable79025
✅ e2e-local-postgres-tanstack-start-79025
✅ e2e-local-prod-nest-stable79025
✅ e2e-local-prod-tanstack-start-79025
✅ e2e-vercel-prod-tanstack-start78026

📋 View full workflow run

Adds allowedDevOrigins entries so portless-style worktree-prefixed
.localhost URLs (e.g. https://<branch>.turbopack.localhost) can hit
HMR and dev-only endpoints without Next's cross-origin protection
flooding the logs with warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

CopilotAI 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.

Pull request overview

Adds new end-to-end coverage for Promise.race behavior between a long-running step and sleep(), and introduces a new dev-tmux skill to standardize a local 3-pane dev setup (workbench + observability UI + scratchpad) using portless for worktree-isolated .localhost URLs.

Changes:

  • Add sleepWinsRaceWorkflow / stepWinsRaceWorkflow workflows to the workbench e2e workflow collection.
  • Add corresponding Vitest e2e tests asserting the expected race winner and duration bounds.
  • Add skills/dev-tmux/SKILL.md documentation for a tmux + portless local dev workflow; add a (currently empty) changeset file.

Reviewed changes

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

FileDescription
workbench/example/workflows/99_e2e.tsAdds two new workflows plus a shared step helper to exercise Promise.race between a step and sleep().
packages/core/e2e/e2e.test.tsAdds two e2e tests covering the new race workflows and asserting winner + duration.
skills/dev-tmux/SKILL.mdNew skill documenting a portless-routed 3-pane tmux setup for local SDK dev + e2e runs.
.changeset/better-pets-reply.mdAdds a new changeset file, but it is currently empty/invalid.

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

Comment on lines +584 to +590
test('sleepWinsRaceWorkflow', { timeout: 60_000 }, async () => {
const run = await start(await e2e('sleepWinsRaceWorkflow'), []);
const returnValue = await run.returnValue;
expect(returnValue.winner).toBe('sleep');
// Sleep is 1s; step would take 10s. Should resolve in ~1s, well under 5s.
expect(returnValue.durationMs).toBeLessThan(5_000);
});

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Obsolete after rebase onto main — PR #1924 fixed the underlying race semantics and added these same tests directly to main. The duplicate set on this branch was dropped (031e4b8); the race tests are no longer part of this PR's diff.

Comment thread.changeset/better-pets-reply.md
Comment threadskills/dev-tmux/SKILL.md Outdated
Comment on lines +51 to +59
tmux send-keys -t "$SESSION".1 \
'cd workbench/nextjs-turbopack && WORKFLOW_PUBLIC_MANIFEST=1 portless run --name turbopack pnpm dev' C-m

# Pane 2 (top-right): observability UI scoped to the workbench app
tmux send-keys -t "$SESSION".2 \
'cd workbench/nextjs-turbopack && portless run --name workflow-obs sh -c "pnpm workflow web --webPort \$PORT --noBrowser"' C-m

# Pane 3 (bottom-right): scratchpad at repo root
tmux send-keys -t "$SESSION".3 'echo "scratchpad: $(pwd)"' C-m

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Fixed in 5a44360. The skill now captures each pane's ID at split time with -P -F '#{pane_id}' and uses those IDs as targets, so it's correct under both pane-base-index 0 and pane-base-index 1 regardless of the user's tmux config.

Comment threadpackages/core/e2e/e2e.test.ts
TooTallNate
TooTallNate previously requested changes May 4, 2026

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verdict: Request Changes

The PR has two things in it: e2e race tests (good idea, surfacing a real V2-handler semantics question) and a personal-dev skill file. I have one blocker on the test, one scope concern on the skill, and one minor nit on the next.config change.


Summary of inline comments

  1. packages/core/e2e/e2e.test.ts:587 — Blocker.sleepWinsRaceWorkflow fails on every e2e job (56+ jobs across all frameworks × all environments). Needs test.fails(...), .skip with TODO, or removal until the underlying race semantics are decided. The PR description correctly identifies this as intentional pending discussion, but the current state makes the e2e matrix unusable for any PR based on this one.

  2. skills/dev-tmux/SKILL.md:3 — Scope. Activation phrases ("spin up the dev session", "start dev mode", "set up the workbench") will trigger for general contributor questions, then push them toward installing portless. Either narrow the phrases to portless-specific terms or move the skill to a user-level location.

  3. workbench/nextjs-turbopack/next.config.ts:13 — Nit. Hardcoding *.turbopack.localhost in the workbench bakes one developer's convention into the shared config. Dev-only, so harmless — flagging for awareness, not blocking.


What I verified

  • Diagnosis is correct: The V2 inline-execution model in workflowEntrypoint (packages/core/src/runtime.ts) executes step bodies inline rather than enqueueing them. A 10-second delayMsStep blocks the handler for 10 seconds, which prevents the 1-second sleep('1s') timer from firing on time. So Promise.race ends up resolving with 'step' because that's what the handler delivers first. Whether this should be considered correct behavior or a bug is a real question.
  • Symlinks: Confirmed 99_e2e.ts is symlinked from workbench/example/workflows/99_e2e.ts into all other workbenches per AGENTS.md convention. ✅
  • Empty changeset: ---\n--- is the right pattern for tests-only PRs that don't ship package changes; matches existing examples (fix-vite-step-source-maps-e2e.md, tanstack-start-workbench.md). ✅
  • Skill frontmatter: version: '1.0' is fine for a new skill (existing migrating-to-workflow-sdk is '0.2.0'). Format matches.
  • Typecheck: Clean across all 40 packages.

Suggested path forward

Smallest change that keeps the value of this PR:

  1. Mark sleepWinsRaceWorkflow with test.fails(...) and add a code comment linking to an issue tracking the V2 race-semantics decision. That preserves the test as a regression detector — if the underlying behavior is ever fixed, the test will go red and force a follow-up.
  2. Optional: open a separate small skill PR after clarifying scope, or move the skill to a personal location.
  3. Land stepWinsRaceWorkflow as a regular passing test — it's a useful regression check on the easy direction.

Happy to re-review once the failing assertion is addressed.

test('sleepWinsRaceWorkflow', { timeout: 60_000 }, async () => {
const run = await start(await e2e('sleepWinsRaceWorkflow'), []);
const returnValue = await run.returnValue;
expect(returnValue.winner).toBe('sleep');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocker — every e2e CI job is failing because of this assertion.

This test fails on every framework's e2e run (express, hono, nitro, fastify, astro, vite, nuxt, sveltekit, tanstack-start, both Next.js variants — local-dev, local-prod, local-postgres, AND vercel-prod) because 99_e2e.ts is shared across all workbenches via symlink. As of the latest run, that's 56+ failed jobs all on expected 'step' to be 'sleep'.

For example: E2E Local Dev Tests (express - stable)FAIL packages/core/e2e/e2e.test.ts > e2e > sleepWinsRaceWorkflow / AssertionError: expected 'step' to be 'sleep'.

The PR description acknowledges this is intentional and wants discussion before merge. That's reasonable as a discussion vehicle, but it can't merge in this state — the PR effectively makes the entire e2e matrix red for any other PR that bases on this one.

Until the underlying behavior question is settled, please pick one:

  1. test.fails(...) — vitest will pass when the assertion fails, and flip to red if/when the runtime is fixed. Cleanly documents the known-broken behavior.
  2. test.skip(...) with a TODO comment linking to the discussion / issue tracking the race semantics decision.
  3. Drop the failing test from this PR and land just stepWinsRaceWorkflow (which is the meaningful regression-prevention test); open a follow-up issue or PR for the slow-step-vs-fast-sleep case once the semantics are decided.

For reference, the underlying cause is consistent with the V2 inline-execution model in packages/core/src/runtime.ts (workflowEntrypoint): the handler executes the step inline rather than enqueuing it, so a 10s step blocks the handler for 10s and prevents the 1s sleep timer from firing on time. That's a real semantics question worth resolving — but in a separate change.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Resolved — merged latest main, which carried PR #1924's fix for the inline-execution behavior and added these same race tests directly. The duplicate set landed by this branch was dropped (commit 031e4b8), so 99_e2e.ts now matches main exactly and the race tests are no longer part of this PR's diff. Both sleepWinsRaceWorkflow and stepWinsRaceWorkflow now run from main's definitions and pass. This PR is dev-tooling only after the rebase.

Comment threadskills/dev-tmux/SKILL.md Outdated
@@ -0,0 +1,125 @@
---
name: dev-tmux
description: Spin up a 3-pane tmux session for local Workflow SDK development — Next.js turbopack workbench, observability UI, and a scratchpad — all routed through portless so each worktree gets isolated `.localhost` URLs. Use when the user asks to "spin up the dev session", "start dev mode", "set up the workbench", or any time they want to test workflows interactively in a worktree alongside the observability UI.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Activation phrases here are very generic — "spin up the dev session", "start dev mode", "set up the workbench" all describe the general task most contributors do daily, not the specific portless+tmux setup this skill describes. As written, this skill will activate for any contributor asking how to start the dev server, then drive them toward installing portless and adopting a personal tmux layout that the rest of the team doesn't use.

A couple of options:

  1. Narrow the activation phrases to portless-specific terms: "portless dev session", "tmux dev layout", "worktree-isolated dev URLs", etc. That way the skill only fires for users who already know they want this specific tooling.
  2. Move it out of the shared skills/ directory. This appears to be one developer's personal workflow tooling rather than a project-wide convention — skills/migrating-to-workflow-sdk/ is documenting a user-facing capability, whereas this is documenting an internal contributor's local-dev preference. A user-level agent skill (e.g. ~/.agents/skills/dev-tmux/) would keep it personal without affecting other contributors.

(The skill itself is well-written and the troubleshooting section is genuinely useful — this is purely about scope/activation, not quality.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Tightened in 5a44360 (v1.1 of the skill). The description now opens with Spin up a portless + tmux dev session ... and explicitly lists the only triggers as portless dev session / tmux dev layout for workflow / worktree-isolated dev URLs / wanting to wire URLs into the Claude statusline. It also explicitly says not to activate for the generic start the dev server / run pnpm dev task. Should now stay out of any contributor's path who isn't already opting into this tooling.

Kept it under skills/ rather than moving to ~/.claude/ because v1.1 also adds a statusline.sh helper script that needs to live next to the skill — moving it out would split the two artifacts. Open to revisiting if option 2 still feels right.

Comment threadworkbench/nextjs-turbopack/next.config.ts
Worktrees get deleted, so wiring the statusline to a worktree path
breaks the moment the worktree is removed. Update the skill and the
script header to recommend pointing `statusLine.command` at the
primary checkout (`$HOME/github/vercel/workflow/...`). The script
itself is already worktree-aware via Claude's `workspace.current_dir`
stdin JSON, so the same invocation surfaces routes for whichever
worktree the session is in.
Bump version to 1.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment threadskills/dev-tmux/SKILL.md Outdated
Comment on lines +5 to +6
author: Vercel Inc.
version: '1.2'

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

change the author to "Pranay Prakash" and set the version to 0.1 for this first release (since it's a new skill anyway)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 8b7225bmetadata.author: Pranay Prakash, metadata.version: '0.1'.

Comment threadskills/dev-tmux/SKILL.md Outdated
@@ -0,0 +1,160 @@
---
name: dev-tmux

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Suggested change
name: dev-tmux
name: internal-dev-workbench

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Applied in 8b7225bgit mv skills/dev-tmux skills/internal-dev-workbench, plus the matching frontmatter name: change and updates to all internal references in SKILL.md and statusline.sh.

@pranaygp

Copy link
Copy Markdown
ContributorAuthor

making some changes to the skill and testing it a bit more. moving to draft for now

pranaygpand others added 4 commits May 5, 2026 10:50
- Statusline overlay now renders `[dev] · [obs] · tmux:<prefix>`,
with the bracketed labels emitted as OSC 8 hyperlinks (clickable in
any modern terminal) styled cyan + underline so they stand out.
Replaces the old long-URL form that was hard to scan and click.
- Add a tmux-session indicator: shown when a session named exactly the
worktree prefix exists (uses `tmux has-session -t =<prefix>` for
exact matching).
- Change the skill's tmux session naming convention from the fixed
`workflow-dev` to `<worktree-prefix>` (basename of the branch — same
string portless uses as the subdomain prefix). This lets the
statusline locate the session deterministically and lets multiple
worktrees run dev sessions concurrently without manual disambiguation.
- Bump skill to v1.3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the abbreviated `tmux:<prefix>` indicator with the full
copy-paste-ready `tmux attach -t <prefix>` invocation. Saves a step
when grabbing the session from another shell.
Bump skill to v1.4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop the dim styling that made the overlay hard to read; use bold
bright cyan + underline for links and bold bright green for the
tmux command.
- Add Nerd Font glyphs: for dev, for obs, for the
tmux copy-paste hint. Falls back to box-drawing if the font lacks
Nerd Font ranges; layout is unaffected.
- Visual differentiation: cyan + underline = clickable hyperlink;
green = copy this command.
Bump skill to v1.5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The copy glyph in `emit_tmux` was a literal Nerd Font byte embedded in
the printf string and got stripped during a prior rewrite. Promote all
three icons (rocket / graph / copy) to top-level shell variables that
use \uHHHH-equivalent UTF-8 escapes, so the source survives editor
round-trips that don't preserve Private Use Area code points.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pranaygp

Copy link
Copy Markdown
ContributorAuthor

Updates since the previous round of review replies — all in skills/dev-tmux/:

Statusline overhaul (commits c714162938c26e)

  • The statusline command should be wired against the primary checkout ($HOME/github/vercel/workflow/skills/dev-tmux/statusline.sh), not a worktree path that disappears on cleanup. The script remains worktree-aware via Claude's workspace.current_dir stdin JSON, so a single stable path correctly surfaces routes for whichever worktree the session is in.
  • Output format reworked from long URLs to compact OSC 8 hyperlinks: [rocket] dev · [graph] obs · [copy] tmux attach -t <prefix>. The bracketed parts are clickable in iTerm2 / Kitty / WezTerm / Terminal.app / Ghostty (cmd/ctrl-click).
  • Bold + bright styling — bold bright cyan + underline for the clickable links, bold bright green for the tmux attach command (visually distinguishes "click this" from "copy this"). Earlier dim styling was hard to read.
  • Nerd Font icons (octicon rocket, octicon graph, fa copy) defined as top-level shell variables using UTF-8 escapes so they survive editor round-trips that don't preserve PUA code points.

Tmux session naming convention (commit 431026c)

  • The skill now creates the session with <worktree-prefix> (basename of the branch — same string portless uses for the <prefix>.<name>.localhost subdomain) instead of the fixed workflow-dev. Lets multiple worktrees run dev sessions concurrently with no manual disambiguation, and lets the statusline locate the session deterministically via tmux has-session -t =<prefix>.
  • The statusline shows the full tmux attach -t <prefix> invocation (commit b0f373c) so it's directly copy-pasteable into another shell.

Skill version bumped to 1.5. Marking ready for review.

…set version
- Rename `skills/dev-tmux/` → `skills/internal-dev-workbench/` to make
the name self-explanatory about the skill's scope (an internal
contributor's local dev workbench, not a generic tmux helper).
- Author: Pranay Prakash. Version: 0.1 (first release of the skill).
- Update internal references in SKILL.md and statusline.sh accordingly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

4 participants

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

Add dev-tmux skill for portless+tmux local Workflow SDK dev - #1916

Merged
pranaygp merged 11 commits into
mainfrom
pgp/stepflow-test
May 5, 2026
Merged

Add dev-tmux skill for portless+tmux local Workflow SDK dev#1916
pranaygp merged 11 commits into
mainfrom
pgp/stepflow-test

Conversation

@pranaygp

@pranaygppranaygp commented May 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • New dev-tmux skill documenting the 3-pane tmux + portless setup for local Workflow SDK development (turbopack workbench, observability UI scoped to that workbench, scratchpad shell — all worktree-isolated via portless's branch-prefixed .localhost URLs).
  • Adds allowedDevOrigins: ['turbopack.localhost', '*.turbopack.localhost'] to workbench/nextjs-turbopack/next.config.ts so portless-style URLs don't get blocked by Next's cross-origin protection in dev (currently floods the logs with HMR warnings).

What changed since the original PR

The original PR also added two race workflows (sleepWinsRaceWorkflow, stepWinsRaceWorkflow) and their e2e tests to surface a Promise.race-vs-replay semantics issue. Those landed independently in #1924 (with the underlying fix), so this branch was rebased onto main and the duplicate additions removed — the race tests now come from main. This PR is dev-tooling only.

Test plan

  • pnpm vitest run packages/core/e2e/e2e.test.ts -t WinsRaceWorkflow against the turbopack workbench (passes after rebase, since [core] V2: skip inline step execution when suspension also has a wait #1924's fix is on main)
  • Spin up the workbench by following skills/dev-tmux/SKILL.md and confirm both https://<branch>.turbopack.localhost (HMR works, no cross-origin warnings) and https://<branch>.workflow-obs.localhost (observability UI shows runs from the workbench)

🤖 Generated with Claude Code

Adds two race workflows (sleepWinsRaceWorkflow, stepWinsRaceWorkflow)
that exercise Promise.race between a step function and a sleep call.
The current `sleepWinsRaceWorkflow` test fails — surfacing how the
replay engine resolves a previously-completed step instantly while
sleep still has to elapse.
Also adds a `dev-tmux` skill that documents the 3-pane tmux + portless
setup for testing workflows interactively in a worktree alongside the
observability UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CopilotAI review requested due to automatic review settings May 4, 2026 11:48
@changeset-bot

changeset-botBot commented May 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8b7225b

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 May 4, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actionsBot commented May 4, 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🥇 Nitro0.031s (-27.8% 🟢)1.005s (~)0.974s101.00x
💻 LocalExpress0.034s (-22.3% 🟢)1.005s (~)0.970s101.11x
🐘 PostgresExpress0.046s (-20.7% 🟢)1.012s (~)0.966s101.48x
🐘 PostgresNitro0.046s (-51.7% 🟢)1.012s (-3.0%)0.966s101.48x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro0.315s (-23.1% 🟢)2.072s (-17.4% 🟢)1.758s101.00x
▲ VercelNext.js (Turbopack)0.793s (+215.3% 🔺)2.620s (+12.3% 🔺)1.827s102.52x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 1 step

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.068s (-5.1% 🟢)2.006s (~)0.938s101.00x
💻 LocalNitro1.073s (-5.2% 🟢)2.006s (~)0.933s101.00x
🐘 PostgresNitro1.083s (-5.0%)2.011s (~)0.927s101.01x
🐘 PostgresExpress1.085s (-5.4% 🟢)2.010s (~)0.925s101.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro1.777s (-54.3% 🟢)3.967s (-32.9% 🟢)2.190s101.00x
▲ VercelNext.js (Turbopack)2.267s (+11.4% 🔺)3.754s (-2.0%)1.487s101.28x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express10.402s (-4.8%)11.020s (~)0.618s31.00x
🐘 PostgresNitro10.402s (-4.3%)11.017s (~)0.615s31.00x
💻 LocalNitro10.414s (-4.9%)11.022s (~)0.607s31.00x
🐘 PostgresExpress10.446s (-4.7%)11.019s (~)0.574s31.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro13.463s (-43.3% 🟢)15.032s (-40.2% 🟢)1.568s21.00x
▲ VercelNext.js (Turbopack)14.098s (-18.6% 🟢)15.843s (-18.3% 🟢)1.745s21.05x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express13.436s (-10.2% 🟢)14.025s (-6.7% 🟢)0.589s51.00x
🐘 PostgresNitro13.445s (-7.9% 🟢)14.018s (-6.7% 🟢)0.573s51.00x
💻 LocalNitro13.481s (-10.5% 🟢)14.027s (-12.5% 🟢)0.546s51.00x
🐘 PostgresExpress13.493s (-7.5% 🟢)14.024s (-6.6% 🟢)0.531s51.00x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro21.085s (-67.3% 🟢)23.044s (-65.4% 🟢)1.959s31.00x
▲ VercelNext.js (Turbopack)22.401s (-57.4% 🟢)24.203s (-55.7% 🟢)1.802s31.06x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro11.880s (-14.9% 🟢)12.016s (-16.0% 🟢)0.136s81.00x
💻 LocalNitro11.939s (-28.9% 🟢)12.147s (-28.7% 🟢)0.208s81.00x
💻 LocalExpress11.974s (-27.9% 🟢)12.396s (-27.2% 🟢)0.422s81.01x
🐘 PostgresExpress12.130s (-13.4% 🟢)12.642s (-13.4% 🟢)0.512s81.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro30.301s (-92.8% 🟢)32.393s (-92.4% 🟢)2.092s31.00x
▲ VercelNext.js (Turbopack)34.399s (-91.3% 🟢)36.050s (-90.9% 🟢)1.651s31.14x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.156s (-22.3% 🟢)2.005s (~)0.849s151.00x
🐘 PostgresNitro1.159s (-9.1% 🟢)2.008s (~)0.849s151.00x
🐘 PostgresExpress1.160s (-8.0% 🟢)2.008s (~)0.848s151.00x
💻 LocalNitro1.180s (-27.6% 🟢)2.006s (-3.3%)0.825s151.02x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.412s (-14.4% 🟢)3.982s (-7.9% 🟢)1.570s81.00x
▲ VercelNext.js (Turbopack)4.906s (+44.4% 🔺)6.610s (+34.0% 🔺)1.704s52.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.228s (-48.0% 🟢)2.008s (-33.3% 🟢)0.780s151.00x
🐘 PostgresNitro1.238s (-47.3% 🟢)2.006s (-33.3% 🟢)0.768s151.01x
💻 LocalExpress1.598s (-45.9% 🟢)2.005s (-41.9% 🟢)0.407s151.30x
💻 LocalNitro1.894s (-39.8% 🟢)2.149s (-44.7% 🟢)0.256s141.54x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.300s (-18.6% 🟢)5.165s (-12.8% 🟢)1.865s61.00x
▲ VercelNext.js (Turbopack)5.590s (-21.3% 🟢)7.240s (-18.7% 🟢)1.651s51.69x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.380s (-60.4% 🟢)2.009s (-49.9% 🟢)0.629s151.00x
🐘 PostgresNitro1.390s (-60.0% 🟢)2.008s (-49.9% 🟢)0.617s151.01x
💻 LocalExpress4.503s (-46.0% 🟢)4.725s (-47.7% 🟢)0.222s73.26x
💻 LocalNitro5.496s (-34.2% 🟢)6.014s (-33.3% 🟢)0.518s53.98x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)5.990s (-32.8% 🟢)7.487s (-31.7% 🟢)1.496s51.00x
▲ VercelNitro6.899s (+95.7% 🔺)10.766s (+94.6% 🔺)3.867s31.15x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Promise.race with 10 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Express1.159s (-7.8% 🟢)2.009s (~)0.850s151.00x
🐘 PostgresNitro1.165s (-7.4% 🟢)2.009s (~)0.844s151.00x
💻 LocalExpress1.317s (-30.5% 🟢)2.005s (-15.2% 🟢)0.689s151.14x
💻 LocalNitro1.415s (-24.1% 🟢)2.007s (-14.3% 🟢)0.591s151.22x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.462s (~)3.978s (-4.6%)1.516s81.00x
▲ VercelNext.js (Turbopack)5.008s (+70.8% 🔺)6.602s (+42.2% 🔺)1.594s52.03x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.221s (-47.8% 🟢)2.009s (-33.3% 🟢)0.788s151.00x
🐘 PostgresExpress1.241s (-47.0% 🟢)2.008s (-33.3% 🟢)0.767s151.02x
💻 LocalExpress1.666s (-46.8% 🟢)2.005s (-46.7% 🟢)0.339s151.36x
💻 LocalNitro2.096s (-31.6% 🟢)2.592s (-33.3% 🟢)0.496s121.72x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.255s (+0.7%)5.219s (+2.8%)1.965s61.00x
▲ VercelNext.js (Turbopack)4.456s (+41.8% 🔺)6.286s (+39.0% 🔺)1.830s51.37x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.358s (-61.0% 🟢)2.007s (-49.9% 🟢)0.649s151.00x
🐘 PostgresExpress1.379s (-60.6% 🟢)2.008s (-49.9% 🟢)0.628s151.02x
💻 LocalExpress4.938s (-43.9% 🟢)5.513s (-40.6% 🟢)0.575s63.64x
💻 LocalNitro6.426s (-29.7% 🟢)6.819s (-32.0% 🟢)0.393s54.73x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.698s (-7.8% 🟢)6.786s (~)2.088s51.00x
▲ VercelNext.js (Turbopack)5.481s (-18.9% 🟢)7.171s (-16.1% 🟢)1.689s51.17x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.429s (-47.8% 🟢)1.006s (~)0.578s601.00x
🐘 PostgresExpress0.435s (-48.2% 🟢)1.007s (-1.6%)0.572s601.01x
💻 LocalExpress0.444s (-54.8% 🟢)1.004s (-6.7% 🟢)0.559s601.04x
💻 LocalNitro0.471s (-52.0% 🟢)1.004s (-8.2% 🟢)0.534s601.10x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro4.925s (-77.7% 🟢)7.020s (-70.8% 🟢)2.095s91.00x
▲ VercelNext.js (Turbopack)6.772s (-53.3% 🟢)8.207s (-49.0% 🟢)1.435s81.37x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.028s (-46.7% 🟢)1.413s (-32.7% 🟢)0.384s641.00x
🐘 PostgresExpress1.065s (-46.1% 🟢)1.771s (-21.5% 🟢)0.706s511.04x
💻 LocalExpress1.125s (-62.7% 🟢)2.005s (-44.1% 🟢)0.880s451.09x
💻 LocalNitro1.184s (-61.0% 🟢)2.006s (-46.6% 🟢)0.822s451.15x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro12.382s (-68.6% 🟢)14.665s (-64.5% 🟢)2.283s71.00x
▲ VercelNext.js (Turbopack)15.639s (-68.6% 🟢)17.695s (-65.8% 🟢)2.056s61.26x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.974s (-51.9% 🟢)2.380s (-48.3% 🟢)0.405s511.00x
🐘 PostgresExpress2.076s (-48.0% 🟢)2.529s (-42.1% 🟢)0.453s481.05x
💻 LocalExpress2.545s (-72.4% 🟢)3.032s (-69.7% 🟢)0.488s401.29x
💻 LocalNitro2.664s (-71.4% 🟢)3.057s (-69.5% 🟢)0.394s401.35x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro38.936s (-59.8% 🟢)41.471s (-57.9% 🟢)2.535s41.00x
▲ VercelNext.js (Turbopack)45.359s (-57.7% 🟢)47.056s (-56.8% 🟢)1.697s31.16x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.186s (-34.3% 🟢)1.005s (~)0.819s601.00x
🐘 PostgresExpress0.200s (-29.0% 🟢)1.006s (~)0.806s601.08x
💻 LocalExpress0.389s (-30.6% 🟢)1.004s (~)0.615s602.09x
💻 LocalNitro0.441s (-27.1% 🟢)1.004s (-1.7%)0.563s602.37x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.370s (+42.7% 🔺)3.938s (+17.5% 🔺)1.568s161.00x
▲ VercelNext.js (Turbopack)3.739s (+84.9% 🔺)5.211s (+37.4% 🔺)1.473s121.58x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.307s (-38.1% 🟢)1.006s (~)0.699s901.00x
🐘 PostgresExpress0.329s (-35.5% 🟢)1.006s (~)0.678s901.07x
💻 LocalExpress2.111s (-16.0% 🟢)2.684s (-10.8% 🟢)0.573s346.88x
💻 LocalNitro2.201s (-13.3% 🟢)2.945s (-2.1%)0.744s317.17x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro7.219s (+123.8% 🔺)9.007s (+86.8% 🔺)1.788s101.00x
▲ VercelNext.js (Turbopack)7.230s (+104.5% 🔺)8.888s (+71.1% 🔺)1.657s111.00x
▲ VercelExpress⚠️missing----

🔍 Observability: Nitro | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.623s (-21.2% 🟢)1.006s (~)0.383s1201.00x
🐘 PostgresExpress0.676s (-17.4% 🟢)1.007s (-1.1%)0.330s1201.09x
💻 LocalExpress8.906s (-20.4% 🟢)9.485s (-20.6% 🟢)0.579s1314.30x
💻 LocalNitro10.253s (-8.4% 🟢)10.694s (-8.3% 🟢)0.441s1216.47x
💻 LocalNext.js (Turbopack)⚠️missing----
🐘 PostgresNext.js (Turbopack)⚠️missing----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Next.js (Turbopack)22.466s (+117.5% 🔺)23.969s (+95.1% 🔺)1.504s61.00x
▲ VercelNitro24.505s (+217.3% 🔺)26.705s (+184.1% 🔺)2.200s51.09x
▲ VercelExpress⚠️missing----

🔍 Observability: Next.js (Turbopack) | Nitro

Stream Benchmarks(includes TTFB metrics)
workflow with stream

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
💻 Local🥇 Express1.120s (+462.6% 🔺)2.004s (+99.5% 🔺)0.009s (-22.3% 🟢)2.016s (+98.0% 🔺)0.896s101.00x
💻 LocalNitro1.134s (+430.7% 🔺)2.006s (+99.7% 🔺)0.012s (-0.8%)2.020s (+98.3% 🔺)0.886s101.01x
🐘 PostgresNitro1.140s (+456.2% 🔺)2.002s (+100.3% 🔺)0.001s (-13.3% 🟢)2.011s (+98.8% 🔺)0.871s101.02x
🐘 PostgresExpress1.159s (+465.2% 🔺)1.998s (+100.1% 🔺)0.001s (-12.5% 🟢)2.011s (+98.8% 🔺)0.852s101.03x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro2.439s (-36.4% 🟢)3.535s (-33.0% 🟢)2.339s (+215.2% 🔺)6.477s (~)4.038s101.00x
▲ VercelNext.js (Turbopack)4.213s (-38.5% 🟢)4.295s (-50.3% 🟢)1.310s (+107.3% 🔺)7.294s (-25.5% 🟢)3.081s101.73x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.488s (+138.4% 🔺)2.003s (+99.0% 🔺)0.004s (-3.3%)2.025s (+98.0% 🔺)0.536s301.00x
💻 LocalNitro1.523s (+81.6% 🔺)2.012s (+98.8% 🔺)0.010s (+5.3% 🔺)2.023s (+81.3% 🔺)0.500s301.02x
🐘 PostgresExpress1.540s (+144.4% 🔺)2.009s (+99.6% 🔺)0.004s (+3.6%)2.026s (+98.0% 🔺)0.486s301.03x
💻 LocalExpress1.665s (+119.9% 🔺)2.009s (+95.2% 🔺)0.009s (-1.0%)2.199s (+111.5% 🔺)0.534s281.12x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.887s (-80.0% 🟢)7.416s (-75.9% 🟢)0.184s (+64.0% 🔺)8.100s (-74.5% 🟢)2.213s81.00x
▲ VercelNext.js (Turbopack)13.623s (-19.5% 🟢)13.512s (-25.9% 🟢)0.199s (-5.7% 🟢)15.368s (-18.8% 🟢)1.744s42.31x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro0.653s (-32.6% 🟢)1.050s (-15.9% 🟢)0.000s (-57.9% 🟢)1.060s (-15.7% 🟢)0.407s571.00x
🐘 PostgresExpress0.672s (-30.0% 🟢)1.033s (-19.2% 🟢)0.000s (+19.0% 🔺)1.042s (-20.2% 🟢)0.370s581.03x
💻 LocalExpress1.274s (+4.0%)2.013s (~)0.000s (-40.0% 🟢)2.015s (~)0.740s301.95x
💻 LocalNitro1.310s (+7.1% 🔺)2.017s (~)0.001s (+400.0% 🔺)2.019s (~)0.709s302.01x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro3.313s (+8.6% 🔺)4.592s (+4.5%)0.000s (+8.3% 🔺)5.023s (+4.5%)1.710s121.00x
▲ VercelNext.js (Turbopack)5.512s (-45.9% 🟢)6.062s (-47.4% 🟢)0.000s (+Infinity% 🔺)6.982s (-42.1% 🟢)1.470s91.66x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

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

💻 Local Development

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
🐘 Postgres🥇 Nitro1.168s (-34.8% 🟢)1.996s (-6.8% 🟢)0.000s (-100.0% 🟢)2.026s (-6.8% 🟢)0.858s301.00x
🐘 PostgresExpress1.377s (-22.3% 🟢)2.066s (-5.1% 🟢)0.000s (NaN%)2.082s (-5.3% 🟢)0.705s291.18x
💻 LocalExpress2.968s (-14.4% 🟢)3.555s (-11.9% 🟢)0.000s (-70.6% 🟢)3.559s (-11.8% 🟢)0.591s172.54x
💻 LocalNitro3.084s (-9.0% 🟢)3.781s (-6.2% 🟢)0.000s (-29.7% 🟢)3.783s (-6.3% 🟢)0.700s162.64x
💻 LocalNext.js (Turbopack)⚠️missing-----
🐘 PostgresNext.js (Turbopack)⚠️missing-----

▲ Production (Vercel)

WorldFrameworkWorkflow TimeTTFBSlurpWall TimeOverheadSamplesvs Fastest
▲ Vercel🥇 Nitro5.124s (+25.2% 🔺)6.356s (+18.3% 🔺)0.000s (-100.0% 🟢)6.813s (+17.6% 🔺)1.689s91.00x
▲ VercelNext.js (Turbopack)7.535s (+34.2% 🔺)8.909s (+27.6% 🔺)0.000s (-100.0% 🟢)9.344s (+23.9% 🔺)1.809s71.47x
▲ VercelExpress⚠️missing-----

🔍 Observability: Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World🥇 Fastest FrameworkWins
💻 LocalExpress18/21
🐘 PostgresNitro17/21
▲ VercelNitro19/21
Fastest World by Framework

Winner determined by most benchmark wins

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

Worlds:

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

📋 View full workflow run


Some benchmark jobs failed:

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

Check the workflow run for details.

@github-actions

github-actionsBot commented May 4, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

All tests passed

Summary

PassedFailedSkippedTotal
✅ ▲ Vercel Production92502191144
✅ 💻 Local Development123702191456
✅ 📦 Local Production123702191456
✅ 🐘 Local Postgres123702191456
✅ 🪟 Windows10400104
✅ 📋 Other5520176728
Total5292010526344

Details by Category

✅ ▲ Vercel Production
AppPassedFailedSkipped
✅ astro78026
✅ example78026
✅ express78026
✅ fastify78026
✅ hono78026
✅ nextjs-turbopack10202
✅ nextjs-webpack10202
✅ nitro78026
✅ nuxt78026
✅ sveltekit9707
✅ vite78026
✅ 💻 Local Development
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 📦 Local Production
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🐘 Local Postgres
AppPassedFailedSkipped
✅ astro-stable79025
✅ express-stable79025
✅ fastify-stable79025
✅ hono-stable79025
✅ nextjs-turbopack-canary85019
✅ nextjs-turbopack-stable-lazy-discovery-disabled10400
✅ nextjs-turbopack-stable-lazy-discovery-enabled10400
✅ nextjs-webpack-canary85019
✅ nextjs-webpack-stable-lazy-discovery-disabled10400
✅ nextjs-webpack-stable-lazy-discovery-enabled10400
✅ nitro-stable79025
✅ nuxt-stable79025
✅ sveltekit-stable9806
✅ vite-stable79025
✅ 🪟 Windows
AppPassedFailedSkipped
✅ nextjs-turbopack10400
✅ 📋 Other
AppPassedFailedSkipped
✅ e2e-local-dev-nest-stable79025
✅ e2e-local-dev-tanstack-start-79025
✅ e2e-local-postgres-nest-stable79025
✅ e2e-local-postgres-tanstack-start-79025
✅ e2e-local-prod-nest-stable79025
✅ e2e-local-prod-tanstack-start-79025
✅ e2e-vercel-prod-tanstack-start78026

📋 View full workflow run

Adds allowedDevOrigins entries so portless-style worktree-prefixed
.localhost URLs (e.g. https://<branch>.turbopack.localhost) can hit
HMR and dev-only endpoints without Next's cross-origin protection
flooding the logs with warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

CopilotAI 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.

Pull request overview

Adds new end-to-end coverage for Promise.race behavior between a long-running step and sleep(), and introduces a new dev-tmux skill to standardize a local 3-pane dev setup (workbench + observability UI + scratchpad) using portless for worktree-isolated .localhost URLs.

Changes:

  • Add sleepWinsRaceWorkflow / stepWinsRaceWorkflow workflows to the workbench e2e workflow collection.
  • Add corresponding Vitest e2e tests asserting the expected race winner and duration bounds.
  • Add skills/dev-tmux/SKILL.md documentation for a tmux + portless local dev workflow; add a (currently empty) changeset file.

Reviewed changes

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

FileDescription
workbench/example/workflows/99_e2e.tsAdds two new workflows plus a shared step helper to exercise Promise.race between a step and sleep().
packages/core/e2e/e2e.test.tsAdds two e2e tests covering the new race workflows and asserting winner + duration.
skills/dev-tmux/SKILL.mdNew skill documenting a portless-routed 3-pane tmux setup for local SDK dev + e2e runs.
.changeset/better-pets-reply.mdAdds a new changeset file, but it is currently empty/invalid.

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

Comment on lines +584 to +590
test('sleepWinsRaceWorkflow', { timeout: 60_000 }, async () => {
const run = await start(await e2e('sleepWinsRaceWorkflow'), []);
const returnValue = await run.returnValue;
expect(returnValue.winner).toBe('sleep');
// Sleep is 1s; step would take 10s. Should resolve in ~1s, well under 5s.
expect(returnValue.durationMs).toBeLessThan(5_000);
});

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Obsolete after rebase onto main — PR #1924 fixed the underlying race semantics and added these same tests directly to main. The duplicate set on this branch was dropped (031e4b8); the race tests are no longer part of this PR's diff.

Comment thread.changeset/better-pets-reply.md
Comment threadskills/dev-tmux/SKILL.md Outdated
Comment on lines +51 to +59
tmux send-keys -t "$SESSION".1 \
'cd workbench/nextjs-turbopack && WORKFLOW_PUBLIC_MANIFEST=1 portless run --name turbopack pnpm dev' C-m

# Pane 2 (top-right): observability UI scoped to the workbench app
tmux send-keys -t "$SESSION".2 \
'cd workbench/nextjs-turbopack && portless run --name workflow-obs sh -c "pnpm workflow web --webPort \$PORT --noBrowser"' C-m

# Pane 3 (bottom-right): scratchpad at repo root
tmux send-keys -t "$SESSION".3 'echo "scratchpad: $(pwd)"' C-m

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Fixed in 5a44360. The skill now captures each pane's ID at split time with -P -F '#{pane_id}' and uses those IDs as targets, so it's correct under both pane-base-index 0 and pane-base-index 1 regardless of the user's tmux config.

Comment threadpackages/core/e2e/e2e.test.ts
TooTallNate
TooTallNate previously requested changes May 4, 2026

@TooTallNateTooTallNate left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verdict: Request Changes

The PR has two things in it: e2e race tests (good idea, surfacing a real V2-handler semantics question) and a personal-dev skill file. I have one blocker on the test, one scope concern on the skill, and one minor nit on the next.config change.


Summary of inline comments

  1. packages/core/e2e/e2e.test.ts:587 — Blocker.sleepWinsRaceWorkflow fails on every e2e job (56+ jobs across all frameworks × all environments). Needs test.fails(...), .skip with TODO, or removal until the underlying race semantics are decided. The PR description correctly identifies this as intentional pending discussion, but the current state makes the e2e matrix unusable for any PR based on this one.

  2. skills/dev-tmux/SKILL.md:3 — Scope. Activation phrases ("spin up the dev session", "start dev mode", "set up the workbench") will trigger for general contributor questions, then push them toward installing portless. Either narrow the phrases to portless-specific terms or move the skill to a user-level location.

  3. workbench/nextjs-turbopack/next.config.ts:13 — Nit. Hardcoding *.turbopack.localhost in the workbench bakes one developer's convention into the shared config. Dev-only, so harmless — flagging for awareness, not blocking.


What I verified

  • Diagnosis is correct: The V2 inline-execution model in workflowEntrypoint (packages/core/src/runtime.ts) executes step bodies inline rather than enqueueing them. A 10-second delayMsStep blocks the handler for 10 seconds, which prevents the 1-second sleep('1s') timer from firing on time. So Promise.race ends up resolving with 'step' because that's what the handler delivers first. Whether this should be considered correct behavior or a bug is a real question.
  • Symlinks: Confirmed 99_e2e.ts is symlinked from workbench/example/workflows/99_e2e.ts into all other workbenches per AGENTS.md convention. ✅
  • Empty changeset: ---\n--- is the right pattern for tests-only PRs that don't ship package changes; matches existing examples (fix-vite-step-source-maps-e2e.md, tanstack-start-workbench.md). ✅
  • Skill frontmatter: version: '1.0' is fine for a new skill (existing migrating-to-workflow-sdk is '0.2.0'). Format matches.
  • Typecheck: Clean across all 40 packages.

Suggested path forward

Smallest change that keeps the value of this PR:

  1. Mark sleepWinsRaceWorkflow with test.fails(...) and add a code comment linking to an issue tracking the V2 race-semantics decision. That preserves the test as a regression detector — if the underlying behavior is ever fixed, the test will go red and force a follow-up.
  2. Optional: open a separate small skill PR after clarifying scope, or move the skill to a personal location.
  3. Land stepWinsRaceWorkflow as a regular passing test — it's a useful regression check on the easy direction.

Happy to re-review once the failing assertion is addressed.

test('sleepWinsRaceWorkflow', { timeout: 60_000 }, async () => {
const run = await start(await e2e('sleepWinsRaceWorkflow'), []);
const returnValue = await run.returnValue;
expect(returnValue.winner).toBe('sleep');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocker — every e2e CI job is failing because of this assertion.

This test fails on every framework's e2e run (express, hono, nitro, fastify, astro, vite, nuxt, sveltekit, tanstack-start, both Next.js variants — local-dev, local-prod, local-postgres, AND vercel-prod) because 99_e2e.ts is shared across all workbenches via symlink. As of the latest run, that's 56+ failed jobs all on expected 'step' to be 'sleep'.

For example: E2E Local Dev Tests (express - stable)FAIL packages/core/e2e/e2e.test.ts > e2e > sleepWinsRaceWorkflow / AssertionError: expected 'step' to be 'sleep'.

The PR description acknowledges this is intentional and wants discussion before merge. That's reasonable as a discussion vehicle, but it can't merge in this state — the PR effectively makes the entire e2e matrix red for any other PR that bases on this one.

Until the underlying behavior question is settled, please pick one:

  1. test.fails(...) — vitest will pass when the assertion fails, and flip to red if/when the runtime is fixed. Cleanly documents the known-broken behavior.
  2. test.skip(...) with a TODO comment linking to the discussion / issue tracking the race semantics decision.
  3. Drop the failing test from this PR and land just stepWinsRaceWorkflow (which is the meaningful regression-prevention test); open a follow-up issue or PR for the slow-step-vs-fast-sleep case once the semantics are decided.

For reference, the underlying cause is consistent with the V2 inline-execution model in packages/core/src/runtime.ts (workflowEntrypoint): the handler executes the step inline rather than enqueuing it, so a 10s step blocks the handler for 10s and prevents the 1s sleep timer from firing on time. That's a real semantics question worth resolving — but in a separate change.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Resolved — merged latest main, which carried PR #1924's fix for the inline-execution behavior and added these same race tests directly. The duplicate set landed by this branch was dropped (commit 031e4b8), so 99_e2e.ts now matches main exactly and the race tests are no longer part of this PR's diff. Both sleepWinsRaceWorkflow and stepWinsRaceWorkflow now run from main's definitions and pass. This PR is dev-tooling only after the rebase.

Comment threadskills/dev-tmux/SKILL.md Outdated
@@ -0,0 +1,125 @@
---
name: dev-tmux
description: Spin up a 3-pane tmux session for local Workflow SDK development — Next.js turbopack workbench, observability UI, and a scratchpad — all routed through portless so each worktree gets isolated `.localhost` URLs. Use when the user asks to "spin up the dev session", "start dev mode", "set up the workbench", or any time they want to test workflows interactively in a worktree alongside the observability UI.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Activation phrases here are very generic — "spin up the dev session", "start dev mode", "set up the workbench" all describe the general task most contributors do daily, not the specific portless+tmux setup this skill describes. As written, this skill will activate for any contributor asking how to start the dev server, then drive them toward installing portless and adopting a personal tmux layout that the rest of the team doesn't use.

A couple of options:

  1. Narrow the activation phrases to portless-specific terms: "portless dev session", "tmux dev layout", "worktree-isolated dev URLs", etc. That way the skill only fires for users who already know they want this specific tooling.
  2. Move it out of the shared skills/ directory. This appears to be one developer's personal workflow tooling rather than a project-wide convention — skills/migrating-to-workflow-sdk/ is documenting a user-facing capability, whereas this is documenting an internal contributor's local-dev preference. A user-level agent skill (e.g. ~/.agents/skills/dev-tmux/) would keep it personal without affecting other contributors.

(The skill itself is well-written and the troubleshooting section is genuinely useful — this is purely about scope/activation, not quality.)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Tightened in 5a44360 (v1.1 of the skill). The description now opens with Spin up a portless + tmux dev session ... and explicitly lists the only triggers as portless dev session / tmux dev layout for workflow / worktree-isolated dev URLs / wanting to wire URLs into the Claude statusline. It also explicitly says not to activate for the generic start the dev server / run pnpm dev task. Should now stay out of any contributor's path who isn't already opting into this tooling.

Kept it under skills/ rather than moving to ~/.claude/ because v1.1 also adds a statusline.sh helper script that needs to live next to the skill — moving it out would split the two artifacts. Open to revisiting if option 2 still feels right.

Comment threadworkbench/nextjs-turbopack/next.config.ts
Worktrees get deleted, so wiring the statusline to a worktree path
breaks the moment the worktree is removed. Update the skill and the
script header to recommend pointing `statusLine.command` at the
primary checkout (`$HOME/github/vercel/workflow/...`). The script
itself is already worktree-aware via Claude's `workspace.current_dir`
stdin JSON, so the same invocation surfaces routes for whichever
worktree the session is in.
Bump version to 1.2.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment threadskills/dev-tmux/SKILL.md Outdated
Comment on lines +5 to +6
author: Vercel Inc.
version: '1.2'

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

change the author to "Pranay Prakash" and set the version to 0.1 for this first release (since it's a new skill anyway)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done in 8b7225bmetadata.author: Pranay Prakash, metadata.version: '0.1'.

Comment threadskills/dev-tmux/SKILL.md Outdated
@@ -0,0 +1,160 @@
---
name: dev-tmux

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Suggested change
name: dev-tmux
name: internal-dev-workbench

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Applied in 8b7225bgit mv skills/dev-tmux skills/internal-dev-workbench, plus the matching frontmatter name: change and updates to all internal references in SKILL.md and statusline.sh.

@pranaygp

Copy link
Copy Markdown
ContributorAuthor

making some changes to the skill and testing it a bit more. moving to draft for now

pranaygpand others added 4 commits May 5, 2026 10:50
- Statusline overlay now renders `[dev] · [obs] · tmux:<prefix>`,
with the bracketed labels emitted as OSC 8 hyperlinks (clickable in
any modern terminal) styled cyan + underline so they stand out.
Replaces the old long-URL form that was hard to scan and click.
- Add a tmux-session indicator: shown when a session named exactly the
worktree prefix exists (uses `tmux has-session -t =<prefix>` for
exact matching).
- Change the skill's tmux session naming convention from the fixed
`workflow-dev` to `<worktree-prefix>` (basename of the branch — same
string portless uses as the subdomain prefix). This lets the
statusline locate the session deterministically and lets multiple
worktrees run dev sessions concurrently without manual disambiguation.
- Bump skill to v1.3.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the abbreviated `tmux:<prefix>` indicator with the full
copy-paste-ready `tmux attach -t <prefix>` invocation. Saves a step
when grabbing the session from another shell.
Bump skill to v1.4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop the dim styling that made the overlay hard to read; use bold
bright cyan + underline for links and bold bright green for the
tmux command.
- Add Nerd Font glyphs: for dev, for obs, for the
tmux copy-paste hint. Falls back to box-drawing if the font lacks
Nerd Font ranges; layout is unaffected.
- Visual differentiation: cyan + underline = clickable hyperlink;
green = copy this command.
Bump skill to v1.5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The copy glyph in `emit_tmux` was a literal Nerd Font byte embedded in
the printf string and got stripped during a prior rewrite. Promote all
three icons (rocket / graph / copy) to top-level shell variables that
use \uHHHH-equivalent UTF-8 escapes, so the source survives editor
round-trips that don't preserve Private Use Area code points.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pranaygp

Copy link
Copy Markdown
ContributorAuthor

Updates since the previous round of review replies — all in skills/dev-tmux/:

Statusline overhaul (commits c714162938c26e)

  • The statusline command should be wired against the primary checkout ($HOME/github/vercel/workflow/skills/dev-tmux/statusline.sh), not a worktree path that disappears on cleanup. The script remains worktree-aware via Claude's workspace.current_dir stdin JSON, so a single stable path correctly surfaces routes for whichever worktree the session is in.
  • Output format reworked from long URLs to compact OSC 8 hyperlinks: [rocket] dev · [graph] obs · [copy] tmux attach -t <prefix>. The bracketed parts are clickable in iTerm2 / Kitty / WezTerm / Terminal.app / Ghostty (cmd/ctrl-click).
  • Bold + bright styling — bold bright cyan + underline for the clickable links, bold bright green for the tmux attach command (visually distinguishes "click this" from "copy this"). Earlier dim styling was hard to read.
  • Nerd Font icons (octicon rocket, octicon graph, fa copy) defined as top-level shell variables using UTF-8 escapes so they survive editor round-trips that don't preserve PUA code points.

Tmux session naming convention (commit 431026c)

  • The skill now creates the session with <worktree-prefix> (basename of the branch — same string portless uses for the <prefix>.<name>.localhost subdomain) instead of the fixed workflow-dev. Lets multiple worktrees run dev sessions concurrently with no manual disambiguation, and lets the statusline locate the session deterministically via tmux has-session -t =<prefix>.
  • The statusline shows the full tmux attach -t <prefix> invocation (commit b0f373c) so it's directly copy-pasteable into another shell.

Skill version bumped to 1.5. Marking ready for review.

…set version
- Rename `skills/dev-tmux/` → `skills/internal-dev-workbench/` to make
the name self-explanatory about the skill's scope (an internal
contributor's local dev workbench, not a generic tmux helper).
- Author: Pranay Prakash. Version: 0.1 (first release of the skill).
- Update internal references in SKILL.md and statusline.sh accordingly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

4 participants

@pranaygp@TooTallNate@VaguelySerious