Skip to content

[world-vercel] Attest the executor's spec version on run_started - #4366

Merged
VaguelySerious merged 7 commits into
mainfrom
peter/executor-spec-version
Sep 25, 2026
Merged

VaguelySerious merged 7 commits into
mainfrom
peter/executor-spec-version

Conversation

@VaguelySerious

@VaguelySerious VaguelySerious commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Companion to #4327 for #4251.

Problem

A run's spec version is stamped by whoever calls start(), and on world-vercel it decides the run's event-id scheme for life. #4327 makes a v5 caller stamp the target deployment's version. It cannot help when the caller is an older SDK that is already deployed: a v4 deployment calling start(wf, [state], { deploymentId: 'latest' }) after the project upgrades to v5 stamps spec 3. The backend then numbers the run's events by ULID, and the v5 runtime executing it fails every delivery (Event id is not slot-numbered) until the run ends with MAX_DELIVERIES_EXCEEDED. This is the handoff pattern the upgrading-workflows cookbook recommends, and the calling code runs on the old deployment, so no v4 release can fix it for runs already in flight.

Change

run_started now carries executorSpecVersion: the spec version this World declared when createWorld ran (mintedSpecVersion(), read once, so it follows WORKFLOW_SEALED_LOG at creation). It is read once so the attestation always matches what the runtime validated; the formal model on #4406 (E1) showed that a per-request env read could over-claim. specVersion on run_started is unchanged and still repeats the caller's stamp, which resilient start relies on.

The world-vercel backend uses it to raise a run stamped lower to this version: before any other event exists when the raise changes the event-id scheme (for example a v4-stamped run moving onto slot ids), and at any point before the run finishes when it only enables newer capabilities. The field is a new optional frame-meta key: a backend that does not know it drops it, so this can ship before or after the backend change, and without it the backend keeps its current behavior.

Only world-vercel is affected at runtime. world-local and world-postgres mint their own event ids and are unchanged.

@workflow/world also gets the classification the backend's raise depends on, next to the constants: CAPABILITY_ONLY_SPEC_VERSIONS (3, 4, 5, 8), STRUCTURAL_SPEC_VERSIONS (2, 6, 7) and crossesStructuralSpecVersion(from, to). A running run may only be raised across capability-only versions. The check is an allow-list, so an unclassified future version is structural by default, and a test fails when a SPEC_VERSION_SUPPORTS_* constant is added without a classification. vercel/workflow-server#1044 mirrors it in version-utils.ts.

Ordering. This has to ship with v5 GA for v4 → v5 handoffs to be repaired from day one. If it slips, the backend could fall back to the client's User-Agent (it already parses @workflow/world-vercel/<version>, and "world-vercel ≥ 5 needs slots" is fixed), but that is only worth building if this misses GA.

Tests

  • events.test.ts: run_started sends executorSpecVersion equal to mintedSpecVersion() next to the caller's specVersion, and follows the sealed-log kill switch.
  • spec-version.test.ts: every SPEC_VERSION_SUPPORTS_* constant is in exactly one set; slot identity and the sealed log are structural; capability-only versions can be crossed; an unclassified future version cannot.
  • pnpm --filter @workflow/world-vercel test: 753 passed. Typecheck clean.

🤖 Generated with Claude Code

run_started repeats the spec version the caller of start() stamped, which
is older when the run was started from another deployment. Send the
version this SDK mints alongside it, so the backend can move such a run
onto the event-id scheme this runtime reads before any event is numbered.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 03e6e67

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

This PR includes changesets to release 21 packages
Name Type
@workflow/world-vercel Patch
@workflow/world Patch
@workflow/cli Patch
@workflow/core Patch
@workflow/web Patch
@workflow/errors Patch
@workflow/vitest Patch
@workflow/web-shared Patch
@workflow/world-local Patch
@workflow/world-postgres Patch
@workflow/world-testing Patch
workflow Patch
@workflow/builders Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/astro Patch
@workflow/nest Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/nuxt Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

❌ Some tests failed

❌ Failed E2E Tests

▲ Vercel Production (62 failed)

python-node (62 failed):

  • promiseAllWorkflow | wrun_41M3BB1B2R0GYJGN16R625X5CJ | 🔍 observability
  • promiseRaceWorkflow | wrun_41M3BB1B2R0GYJGN16R625X5CK | 🔍 observability
  • promiseAnyWorkflow | wrun_41M3BB1B2S0GQC8GDCY4Q2RS5K | 🔍 observability
  • retainedInterleavingWorkflow | wrun_41M3BB1J4C0GTYQS7ZE88T6HGS | 🔍 observability
  • hookWorkflow | wrun_41M3BB1QY20GRCG431R34R73QM | 🔍 observability
  • hookWorkflow is not resumable via public webhook endpoint | wrun_41M3BB1RG60GHKHGRT9X9X0Q36 | 🔍 observability
  • sleepingWorkflow | wrun_41M3BB20V20GNX1GQ35TDWAY7E | 🔍 observability
  • parallelSleepWorkflow | wrun_41M3BB20YJ0GN3WT3XAM02SJ1W | 🔍 observability
  • sleepWinsRaceWorkflow | wrun_41M3BB22K80GS5P7PCDCYA3Z58 | 🔍 observability
  • stepWinsRaceWorkflow | wrun_41M3BB25PC0GNWQBRQ9DQME5A1 | 🔍 observability
  • nullByteWorkflow | wrun_41M3BB27RP0GWRC7KYNB7AD6WA | 🔍 observability
  • outputStreamWorkflow no startIndex (reads all chunks)
  • outputStreamWorkflow positive startIndex (skips first chunk)
  • outputStreamWorkflow negative startIndex (reads from end)
  • outputStreamWorkflow - getTailIndex and getChunks getTailIndex returns correct index after stream completes
  • outputStreamWorkflow - getTailIndex and getChunks getChunks returns same content as reading the stream
  • outputStreamInsideStepWorkflow - getWritable() called inside step functions | wrun_41M3BB2T370GQ81ZFTBT412N7T | 🔍 observability
  • utf8StreamWorkflow | wrun_41M3BB2WEC0GX6BS5SRH5Z2ERS | 🔍 observability
  • writableForwardedFromWorkflowWorkflow | wrun_41M3BB2YB60GNCDG74Q4NKXWCX | 🔍 observability
  • writableForwardedFromStepWorkflow | wrun_41M3BB2Z8B0GNCQKTCD34K2ZCR | 🔍 observability
  • promiseRaceStressTestWorkflow | wrun_41M3BB317S0GJDY5NQ225VVE1D | 🔍 observability
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • error handling catchability step throw round-trips FatalError with cause chain to workflow catch
  • error handling catchability workflow throw round-trips FatalError + cause through run_failed event
  • error handling not registered StepNotRegisteredError fails the step but workflow can catch it
  • error handling not registered StepNotRegisteredError fails the run when not caught in workflow
  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M3BB4J4M0GGDP8426R6F6FFG | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M3BB4F7F0GR6SBCSDA8CPEAT | 🔍 observability
  • hookGetConflictWorkflow - awaiting hook.getConflict() registers hook without payload | wrun_41M3BB4ETZ0GKYTEQMQN60X02H | 🔍 observability
  • 'hookGetConflictWithPriorStepWorkflow' - hook.getConflict() does not block step execution | wrun_41M3BB4GS60GV117ZR9QJT7350 | 🔍 observability
  • 'hookGetConflictWithParallelStepWorkfl…' - hook.getConflict() does not block step execution | wrun_41M3BB4M1G0GGY4JZB3V6YN401 | 🔍 observability
  • hookGetConflictThenStepParallelWorkflow - hook.getConflict() continuation step runs alongside other steps | wrun_41M3BB4M5Y0GY2MXH9S4TBCZXT | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M3BB4RJK0GT8AKJNBA9YXMC1 | 🔍 observability
  • hookClaimOnlyMutexWorkflow - hook works as a pure run mutex without payload data | wrun_41M3BB5JMM0GVV46R5DZCBC5YD | 🔍 observability
  • hookAdoptOwnerResultWorkflow - duplicate adopts the owner result via conflict.returnValue | wrun_41M3BB57H10GXYRQ0FDC9H41EW | 🔍 observability
  • hookSignalOwnerWorkflow - duplicate forwards its payload to the owner via resumeHook | wrun_41M3BB57JD0GHEP7NG6N1FS5EQ | 🔍 observability
  • resume-or-start route pattern - resumeHook retried after start() reaches the new run | wrun_41M3BB5XYD0GQBMHVH00FKQ21K | 🔍 observability
  • hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running | wrun_41M3BB629T0GTBYQ4FVAC26RB7 | 🔍 observability
  • hookTokenReuseLoopWorkflow - same run recreates a hook with the same token after dispose() | wrun_41M3BB601X0GRFA8YP7RV6CWCB | 🔍 observability
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step | wrun_41M3BB6MW20GYFJG3XWEYQJX4N | 🔍 observability
  • cancelRun - cancelling a running workflow | wrun_41M3BB7HK90GZJBWSY2NDF0MZK | 🔍 observability
  • cancelRun via CLI - cancelling a running workflow | wrun_41M3BB7KYQ0GNRAFMJHCFGGK78 | 🔍 observability
  • hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep | wrun_41M3BB7VFY0GMWBCT35Q6H0C43 | 🔍 observability
  • hookWithSleepFinalStepWorkflow - step only on final payload | wrun_41M3BB7W5V0GGNHJQEVQRC50HS | 🔍 observability
  • sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration | wrun_41M3BB7YGD0GZDKA54J7KEBKVT | 🔍 observability
  • sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) | wrun_41M3BB7Z7X0GVZ766DMBYSGAZK | 🔍 observability
  • AbortController abortTimeoutWorkflow: timeout cancels long-running step
  • AbortController abortParallelWorkflow: abort cancels all parallel steps
  • AbortController abortReasonWorkflow: abort reason preserved across boundaries
  • AbortController abortViaHookWorkflow: external hook triggers abort on in-flight step
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_41M3BB8GJE0GW08SA6CFDFS7KG | 🔍 observability
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41M3BB8HV20GRTAR6MCKZJ67Q6 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes Promise.all of disjoint-key writes: every key lands
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run

⚠️ Flaky E2E Tests (passed on retry)

These tests failed at least once and passed on a retry. A recurring entry here is a real race worth investigating.

  • negative startIndex (reads from end) (express · vercel-prod / vercel / node / production)

🛠 Infra Events (absorbed by the harness)

Platform anomalies the e2e harness detected and worked around (e.g. a run the queue never picked up, replaced by a fresh run). Clustered timestamps indicate a backend blip; a steady drip indicates a platform issue worth escalating.

124 infra events
  • cold-start-warmup · suite warmup (python) · at 03:53:04Z · abandoned wrun_41M3BB0AHY0GJ0Y6C0NY6P1EQS · (+7 more)
  • run-pickup-stall · promiseAllWorkflow (python) · at 03:53:20Z · abandoned wrun_41M3BB3ZBB0GPTTGFSM6CMFJ1S
  • run-pickup-stall · promiseAnyWorkflow (python) · at 03:53:20Z · abandoned wrun_41M3BB3ZBB0GPTTGFSM6CMFJ1V
  • run-pickup-stall · promiseRaceWorkflow (python) · at 03:53:20Z · abandoned wrun_41M3BB3ZBB0GPTTGFSM6CMFJ1T
  • run-pickup-stall · retainedInterleavingWorkflow (python) · at 03:53:21Z · abandoned wrun_41M3BB3ZBB0GPTTGFSM6CMFJ1W
  • run-pickup-stall · hookWorkflow (python) · at 03:53:21Z · abandoned wrun_41M3BB3ZBG0GVJMDYBC0EHSZTG
  • run-pickup-stall · hookWorkflow (python) · at 03:54:08Z · abandoned wrun_41M3BB5ESV0GZRN4JBJR2CJPR4
  • run-pickup-stall · retainedInterleavingWorkflow (python) · at 03:54:09Z · abandoned wrun_41M3BB5F1P0GJZTYAKGN3DWRXN
  • run-pickup-stall · promiseAllWorkflow (python) · at 03:54:20Z · abandoned wrun_41M3BB5TP50GMP7X6WNSWA6R8R
  • run-pickup-stall · promiseRaceWorkflow (python) · at 03:54:21Z · abandoned wrun_41M3BB5TJY0GGZVWXE0GYBB0TG
  • run-pickup-stall · promiseAnyWorkflow (python) · at 03:54:21Z · abandoned wrun_41M3BB5TKY0GVC7D3K444A0SBV
  • run-pickup-stall · hookWorkflow is not resumable via public webhook endpoint (python) · at 03:54:55Z · abandoned wrun_41M3BB6WH30GHN8BY3XH2CZWXM
  • run-pickup-stall · sleepingWorkflow (python) · at 03:54:56Z · abandoned wrun_41M3BB6X150GVKZQGDNRDQVB1J
  • cold-start-warmup · suite warmup (tanstack-start) · at 03:55:06Z · abandoned wrun_01M3BB77VHQ437913S4XANFD0A
  • run-pickup-stall · parallelSleepWorkflow (python) · at 03:55:22Z · abandoned wrun_41M3BB7PA30GNCZHKX1AVY0MNQ
  • run-pickup-stall · sleepWinsRaceWorkflow (python) · at 03:55:23Z · abandoned wrun_41M3BB7PGM0GR5YA1PQEYJ8Q5X
  • run-pickup-stall · stepWinsRaceWorkflow (python) · at 03:55:23Z · abandoned wrun_41M3BB7PN20GWXB0XM60MN3WBA
  • run-pickup-stall · sleepingWorkflow (python) · at 03:56:51Z · abandoned wrun_41M3BB9DTJ0GXD16E1670CNA5W
  • run-pickup-stall · hookWorkflow is not resumable via public webhook endpoint (python) · at 03:56:51Z · abandoned wrun_41M3BB9E1F0GV7CS53NB0J2Q15
  • run-pickup-stall · sleepWinsRaceWorkflow (python) · at 03:56:58Z · abandoned wrun_41M3BBAED50GTT1MPM1GAFFK2V
  • run-pickup-stall · parallelSleepWorkflow (python) · at 03:56:58Z · abandoned wrun_41M3BBAE6M0GWTVXYQRT9PP3QH
  • run-pickup-stall · stepWinsRaceWorkflow (python) · at 03:56:58Z · abandoned wrun_41M3BBAEHM0GPD3458PJARNZ4E
  • run-pickup-stall · nullByteWorkflow (python) · at 03:57:19Z · abandoned wrun_41M3BBB90P0GZ2JE7JFY801RK7
  • run-pickup-stall · no startIndex (reads all chunks) (python) · at 03:57:19Z · abandoned wrun_41M3BBB9960GGT37PGV9HGHCNC
  • run-pickup-stall · positive startIndex (skips first chunk) (python) · at 03:57:51Z · abandoned wrun_41M3BBC8SK0GXRJS8V7DCP15SV
  • run-pickup-stall · negative startIndex (reads from end) (python) · at 03:57:52Z · abandoned wrun_41M3BBC9050GZSJ0EXBD17XZF9
  • run-pickup-stall · getTailIndex returns correct index after stream completes (python) · at 03:57:54Z · abandoned wrun_41M3BBCB3P0GTWPYAJGRPYEZ4G
  • run-pickup-stall · getTailIndex returns -1 before any chunks are written (python) · at 03:58:19Z · abandoned wrun_41M3BBD3KQ0GYFWQP0G2H1K27H
  • run-pickup-stall · getChunks returns same content as reading the stream (python) · at 03:58:19Z · abandoned wrun_41M3BBD3W60GH0YCT6FPEZDY7R
  • run-pickup-stall · outputStreamInsideStepWorkflow - getWritable() called inside step functions (python) · at 03:58:35Z · abandoned wrun_41M3BBDKAR0GREKS1BQ8WCQ2PJ
  • run-pickup-stall · utf8StreamWorkflow (python) · at 03:58:51Z · abandoned wrun_41M3BBE3CM0GV6A1HMZ60K2NT1
  • run-pickup-stall · no startIndex (reads all chunks) (python) · at 03:58:54Z · abandoned wrun_41M3BBE5VG0GPMNWBDK8ZWVX0F
  • run-pickup-stall · nullByteWorkflow (python) · at 03:58:54Z · abandoned wrun_41M3BBE5PN0GS8ETYQZDBDK4YJ
  • run-pickup-stall · positive startIndex (skips first chunk) (python) · at 03:59:19Z · abandoned wrun_41M3BBEYF60GT86CBW4JHYJ60A
  • run-pickup-stall · negative startIndex (reads from end) (python) · at 03:59:36Z · abandoned wrun_41M3BBFEGP0GZZ3MNRBF8NMSKE
  • run-pickup-stall · getTailIndex returns correct index after stream completes (python) · at 03:59:54Z · abandoned wrun_41M3BBG0VB0GSKKZ4M5DX3E1GC
  • run-pickup-stall · getChunks returns same content as reading the stream (python) · at 03:59:56Z · abandoned wrun_41M3BBG25Y0GVF9Z26Q0Q6R4GS
  • run-pickup-stall · hookCleanupTestWorkflow - hook token reuse after workflow completion (nextjs-webpack) · at 04:00:17Z · abandoned wrun_01M3BBGQPN4J1DEJTSXSXXXRX7
  • run-pickup-stall · outputStreamInsideStepWorkflow - getWritable() called inside step functions (python) · at 04:00:19Z · abandoned wrun_41M3BBGS260GTMXA4KFCCPBD96
  • run-pickup-stall · writableForwardedFromWorkflowWorkflow (python) · at 04:00:36Z · abandoned wrun_41M3BBH93Q0GGGQEEEFH0VTWR6
  • run-pickup-stall · writableForwardedFromStepWorkflow (python) · at 04:00:53Z · abandoned wrun_41M3BBHTD80GQC49E60QJV5TE1
  • run-pickup-stall · utf8StreamWorkflow (python) · at 04:00:55Z · abandoned wrun_41M3BBHVEB0GXFQF67DBCJE2EX
  • run-pickup-stall · promiseRaceStressTestWorkflow (python) · at 04:00:57Z · abandoned wrun_41M3BBHXXP0GR3FZS66GC62EGW
  • run-pickup-stall · regular Error retries until success (python) · at 04:01:20Z · abandoned wrun_41M3BBJM9B0GKF3W6R62N48EJW
  • run-pickup-stall · FatalError fails immediately without retries (python) · at 04:01:57Z · abandoned wrun_41M3BBKRGQ0GY5DFQJV0364QFN
  • run-pickup-stall · RetryableError respects custom retryAfter delay (python) · at 04:02:20Z · abandoned wrun_41M3BBMEWC0GG1R8V3GA67CD55
  • run-pickup-stall · maxRetries=0 disables retries (python) · at 04:02:36Z · abandoned wrun_41M3BBMY9R0GZRRWASSGY266MX
  • run-pickup-stall · FatalError can be caught and detected with FatalError.is() (python) · at 04:02:53Z · abandoned wrun_41M3BBNFK90GMZ8ZEEF9Y8TW0J
  • run-pickup-stall · step throw round-trips FatalError with cause chain to workflow catch (python) · at 04:02:54Z · abandoned wrun_41M3BBNGMB0GSNCQSZC371A88W
  • run-pickup-stall · workflow throw round-trips FatalError + cause through run_failed event (python) · at 04:02:57Z · abandoned wrun_41M3BBNK3Q0GTAHQ4VP4NVRXKQ
  • run-pickup-stall · StepNotRegisteredError fails the step but workflow can catch it (python) · at 04:03:20Z · abandoned wrun_41M3BBP9FC0GVHZYHWQVKTBVAT
  • run-pickup-stall · StepNotRegisteredError fails the run when not caught in workflow (python) · at 04:03:36Z · abandoned wrun_41M3BBPRWR0GQ0ERTPP1E55VJ0
  • run-pickup-stall · promiseRaceStressTestWorkflow (python) · at 04:05:07Z · abandoned wrun_41M3BBRJPT0GJ8FVFJ01M5BP2K
  • run-pickup-stall · regular Error retries until success (python) · at 04:05:08Z · abandoned wrun_41M3BBRK6Q0GJ1QERVPRS5XYJS
  • run-pickup-stall · writableForwardedFromWorkflowWorkflow (python) · at 04:05:08Z · abandoned wrun_41M3BBRKFR0GK4TNW121MH1VF4
  • run-pickup-stall · writableForwardedFromStepWorkflow (python) · at 04:05:10Z · abandoned wrun_41M3BBS5RJ0GM657QX0KQXH1JA
  • run-pickup-stall · hookCleanupTestWorkflow - hook token reuse after workflow completion (python) · at 04:05:31Z · abandoned wrun_41M3BBS8NW0GRDHQYRB5HV72T9
  • run-pickup-stall · FatalError fails immediately without retries (python) · at 04:06:11Z · abandoned wrun_41M3BBTD9T0GRF4T4MVCNFE1S9
  • run-pickup-stall · RetryableError respects custom retryAfter delay (python) · at 04:06:11Z · abandoned wrun_41M3BBTDSQ0GKD3JXPFR6AEDBV
  • run-pickup-stall · maxRetries=0 disables retries (python) · at 04:06:37Z · abandoned wrun_41M3BBV38W0GQXGRRE340TWH3W
  • run-pickup-stall · FatalError can be caught and detected with FatalError.is() (python) · at 04:06:49Z · abandoned wrun_41M3BBW7WT0GZR8B7TEZ7GMF60
  • run-pickup-stall · step throw round-trips FatalError with cause chain to workflow catch (python) · at 04:06:49Z · abandoned wrun_41M3BBW8NR0GYJ7W5VB4AJSF5B
  • run-pickup-stall · workflow throw round-trips FatalError + cause through run_failed event (python) · at 04:06:56Z · abandoned wrun_41M3BBWSQF0GPJCT541NJAZM64
  • run-pickup-stall · StepNotRegisteredError fails the step but workflow can catch it (python) · at 04:06:57Z · abandoned wrun_41M3BBWTYJ0GHYNJV51TRMEXQD
  • run-pickup-stall · StepNotRegisteredError fails the run when not caught in workflow (python) · at 04:07:07Z · abandoned wrun_41M3BBX7P80GHDBF9TJ3FEY7YB
  • run-pickup-stall · hookCleanupTestWorkflow - hook token reuse after workflow completion (python) · at 04:07:36Z · abandoned wrun_41M3BBY3RJ0GS2630GX7F8QKX3
  • run-pickup-stall · hookGetConflictWorkflow - awaiting hook.getConflict() registers hook without payload (python) · at 04:07:53Z · abandoned wrun_41M3BBYMAF0GN2ADG619B6DQ66
  • run-pickup-stall · 'hookGetConflictWithPriorStepWorkflow' - hook.getConflict() does not block step execution (python) · at 04:07:55Z · abandoned wrun_41M3BBYP9G0GRAYXPQT1J2JFN1
  • run-pickup-stall · 'hookGetConflictWithParallelStepWorkfl…' - hook.getConflict() does not block step execution (python) · at 04:08:08Z · abandoned wrun_41M3BBZ37C0GZDEFMGF730C2AA
  • run-pickup-stall · hookGetConflictThenStepParallelWorkflow - hook.getConflict() continuation step runs alongside other steps (python) · at 04:08:23Z · abandoned wrun_41M3BBZHED0GM90Q9VX9S6KTK0
  • run-pickup-stall · hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered (python) · at 04:08:39Z · abandoned wrun_41M3BC019F0GY148C00E6TGF48
  • run-pickup-stall · hookGetConflictWorkflow - awaiting hook.getConflict() registers hook without payload (python) · at 04:08:54Z · abandoned wrun_41M3BC0FMD0GNBR36W7T5XZ237
  • run-pickup-stall · 'hookGetConflictWithPriorStepWorkflow' - hook.getConflict() does not block step execution (python) · at 04:08:56Z · abandoned wrun_41M3BC0HDC0GTSJHYEY6Z1R7FP
  • run-pickup-stall · 'hookGetConflictWithParallelStepWorkfl…' - hook.getConflict() does not block step execution (python) · at 04:09:09Z · abandoned wrun_41M3BC0YCV0GQ6BKVRXMSV9B2Q
  • run-pickup-stall · hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered (python) · at 04:09:26Z · abandoned wrun_41M3BC1EXT0GQADDP3V2Z57VV4
  • run-pickup-stall · hookGetConflictThenStepParallelWorkflow - hook.getConflict() continuation step runs alongside other steps (python) · at 04:09:53Z · abandoned wrun_41M3BC29Y10GSD0RJXPFB41Y1A
  • run-pickup-stall · hookClaimOnlyMutexWorkflow - hook works as a pure run mutex without payload data (python) · at 04:09:55Z · abandoned wrun_41M3BC2ATT0GTFZJW527GKHVKQ
  • run-pickup-stall · hookAdoptOwnerResultWorkflow - duplicate adopts the owner result via conflict.returnValue (python) · at 04:09:57Z · abandoned wrun_41M3BC2CMH0GRTPQPTHQPJAN2F
  • run-pickup-stall · hookSignalOwnerWorkflow - duplicate forwards its payload to the owner via resumeHook (python) · at 04:10:10Z · abandoned wrun_41M3BC2SPF0GJA2H92W5QNDE34
  • run-pickup-stall · resume-or-start route pattern - resumeHook retried after start() reaches the new run (python) · at 04:10:13Z · abandoned wrun_41M3BC2X1R0GNKM85H72VH123M
  • run-pickup-stall · hookClaimOnlyMutexWorkflow - hook works as a pure run mutex without payload data (python) · at 04:10:41Z · abandoned wrun_41M3BC3RM60GQ26224YFMPQY27
  • run-pickup-stall · hookAdoptOwnerResultWorkflow - duplicate adopts the owner result via conflict.returnValue (python) · at 04:10:43Z · abandoned wrun_41M3BC3THZ0GN4SVW8TA6FV5CZ
  • run-pickup-stall · hookSignalOwnerWorkflow - duplicate forwards its payload to the owner via resumeHook (python) · at 04:10:57Z · abandoned wrun_41M3BC47G90GYEQ28RHGVPCKBP
  • run-pickup-stall · resume-or-start route pattern - resumeHook retried after start() reaches the new run (python) · at 04:11:00Z · abandoned wrun_41M3BC4BE00GN2VGTVNEHH0XEZ
  • run-pickup-stall · hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running (python) · at 04:11:24Z · abandoned wrun_41M3BC52E30GR8FFY6P9NRJT02
  • run-pickup-stall · hookTokenReuseLoopWorkflow - same run recreates a hook with the same token after dispose() (python) · at 04:11:28Z · abandoned wrun_41M3BC567S0GPQ881Q0PXV3N5P
  • run-pickup-stall · spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step (python) · at 04:11:30Z · abandoned wrun_41M3BC582D0GKTF24S02GBWG1Q
  • run-pickup-stall · cancelRun - cancelling a running workflow (python) · at 04:11:47Z · abandoned wrun_41M3BC5S7Y0GXVXG7GH17MZTVM
  • run-pickup-stall · cancelRun via CLI - cancelling a running workflow (python) · at 04:11:49Z · abandoned wrun_41M3BC5TZE0GT0Z36JNB97G4W9
  • run-pickup-stall · hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running (python) · at 04:12:11Z · abandoned wrun_41M3BC6G4R0GN3TMVJAFN7J2C9
  • run-pickup-stall · hookTokenReuseLoopWorkflow - same run recreates a hook with the same token after dispose() (python) · at 04:12:14Z · abandoned wrun_41M3BC6KNB0GHGJQ4BK2B1Q4YY
  • run-pickup-stall · cancelRun - cancelling a running workflow (python) · at 04:12:19Z · abandoned wrun_41M3BC6R3W0GXEYKENDCBWSN8Q
  • run-pickup-stall · cancelRun via CLI - cancelling a running workflow (python) · at 04:12:21Z · abandoned wrun_41M3BC6T050GJ7CFSVHZ6YV0MA
  • run-pickup-stall · hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep (python) · at 04:12:51Z · abandoned wrun_41M3BC7QA10GK61J5C57CXBSMA
  • run-pickup-stall · hookWithSleepFinalStepWorkflow - step only on final payload (python) · at 04:12:53Z · abandoned wrun_41M3BC7RZV0GG0G52Y661VCVCN
  • run-pickup-stall · sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration (python) · at 04:12:58Z · abandoned wrun_41M3BC7XRA0GH919RCC031GQGM
  • run-pickup-stall · sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) (python) · at 04:13:01Z · abandoned wrun_41M3BC81G00GNNMXM7EH40Z973
  • run-pickup-stall · spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step (python) · at 04:13:31Z · abandoned wrun_41M3BC8XRM0GXASW8QR6T912PJ
  • run-pickup-stall · hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep (python) · at 04:13:38Z · abandoned wrun_41M3BC952T0GN1FFCEQDCTTTMX
  • run-pickup-stall · hookWithSleepFinalStepWorkflow - step only on final payload (python) · at 04:13:40Z · abandoned wrun_41M3BC96WX0GZBHRWXBKY1K6AQ
  • run-pickup-stall · sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration (python) · at 04:13:59Z · abandoned wrun_41M3BC9RXN0GP032HSP6QQPRW4
  • run-pickup-stall · sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) (python) · at 04:14:03Z · abandoned wrun_41M3BC9WMY0GYRDE5Y9AAHACZW
  • run-pickup-stall · abortTimeoutWorkflow: timeout cancels long-running step (python) · at 04:14:25Z · abandoned wrun_41M3BCAK2K0GT2HDG7BP2T7K3V
  • run-pickup-stall · abortParallelWorkflow: abort cancels all parallel steps (python) · at 04:14:27Z · abandoned wrun_41M3BCAM200GY93WXBR7KMRAPM
  • run-pickup-stall · abortReasonWorkflow: abort reason preserved across boundaries (python) · at 04:15:12Z · abandoned wrun_41M3BCBKGN0GXJ7THYSBYE8V7Q
  • run-pickup-stall · abortViaHookWorkflow: external hook triggers abort on in-flight step (python) · at 04:15:45Z · abandoned wrun_41M3BCCDNK0GKCXEGCEG7X2SXA
  • run-pickup-stall · metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (Vercel World Regression: getWorkflowMetadata() fully breaking runs #1577) (python) · at 04:15:48Z · abandoned wrun_41M3BCCFD10GV7ES9XC836Q1GW
  • run-pickup-stall · abortTimeoutWorkflow: timeout cancels long-running step (python) · at 04:16:46Z · abandoned wrun_41M3BCDJV80GXZ5VA5SERK4NJH
  • run-pickup-stall · abortParallelWorkflow: abort cancels all parallel steps (python) · at 04:17:09Z · abandoned wrun_41M3BCEA010GSX3SEK3TNNDC08
  • run-pickup-stall · start: initial attributes are seeded on run creation (python) · at 04:17:21Z · abandoned wrun_41M3BCF2C80GVADJ8D3AA101SB
  • run-pickup-stall · start: reserved-prefix initial attributes are seeded with allowReservedAttributes (python) · at 04:17:22Z · abandoned wrun_41M3BCF7KA0GJSR6ESP4JM711Q
  • run-pickup-stall · setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly (python) · at 04:17:33Z · abandoned wrun_41M3BCFDE80GRS3N4J01G3RDK0
  • run-pickup-stall · setAttributesInsideStepWorkflow: step-body calls append attributed native events (python) · at 04:17:36Z · abandoned wrun_41M3BCFY9D0GSV07FNRCTGEAYF
  • run-pickup-stall · abortReasonWorkflow: abort reason preserved across boundaries (python) · at 04:17:40Z · abandoned wrun_41M3BCG4K10GVDXCMECJ9DWG63
  • run-pickup-stall · abortViaHookWorkflow: external hook triggers abort on in-flight step (python) · at 04:17:41Z · abandoned wrun_41M3BCG4WT0GGRWTFYBGVYHQGN
  • run-pickup-stall · metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (Vercel World Regression: getWorkflowMetadata() fully breaking runs #1577) (python) · at 04:17:44Z · abandoned wrun_41M3BCGC3D0GGQ9WYWBT9DYAFX
  • run-pickup-stall · Promise.all of disjoint-key writes: every key lands (python) · at 04:17:52Z · abandoned wrun_41M3BCGVJY0GZ576SDHKSR43FC
  • run-pickup-stall · start: initial attributes are seeded on run creation (python) · at 04:18:09Z · abandoned wrun_41M3BCHDN90GJ7F3X35GKE5FX1
  • run-pickup-stall · start: reserved-prefix initial attributes are seeded with allowReservedAttributes (python) · at 04:18:20Z · abandoned wrun_41M3BCHRWF0GPM777GX1D8EVCS
  • run-pickup-stall · setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly (python) · at 04:18:26Z · abandoned wrun_41M3BCHYYE0GVGCV4QMJRR0FHN
  • run-pickup-stall · setAttributesInsideStepWorkflow: step-body calls append attributed native events (python) · at 04:18:27Z · abandoned wrun_41M3BCHZCY0GSQ2VVTS0BQDD5H
  • run-pickup-stall · Promise.all of disjoint-key writes: every key lands (python) · at 04:18:37Z · abandoned wrun_41M3BCJ8W70GYQGHW3X3F65P8P
  • run-pickup-stall · workflow throws after awaited setAttributes: attribute still persists on the failed run (python) · at 04:18:39Z · abandoned wrun_41M3BCJBJ40GGD0V730TH8ZYED
  • run-pickup-stall · workflow throws after awaited setAttributes: attribute still persists on the failed run (python) · at 04:19:10Z · abandoned wrun_41M3BCK9CM0GMMHGGYXDV3F4M7

E2E Test Summary

Summary
Passed Failed Skipped Total
❌ ▲ Vercel Production 3816 62 739 4617
✅ 💻 Local Development 4238 0 550 4788
✅ 📦 Local Production 4238 0 550 4788
✅ 🐘 Local Postgres 4238 0 550 4788
✅ 🪟 Windows 340 0 2 342
✅ vercel-http-transport 873 0 153 1026
✅ vercel-multi-region 27 0 0 27
✅ vercel-ws-transport 591 0 93 684
Total 18361 62 2637 21060
Details by Category

❌ ▲ Vercel Production

App Passed Failed Skipped
✅ astro-node 141 0 30
✅ astro-quickjs 141 0 30
✅ example-node 141 0 30
✅ example-quickjs 141 0 30
✅ express-node 141 0 30
✅ express-quickjs 141 0 30
✅ fastify-node 141 0 30
✅ fastify-quickjs 141 0 30
✅ hono-node 141 0 30
✅ hono-quickjs 141 0 30
✅ nest-node 141 0 30
✅ nest-quickjs 141 0 30
✅ nextjs-turbopack-node 168 0 3
✅ nextjs-turbopack-quickjs 168 0 3
✅ nextjs-webpack-node 168 0 3
✅ nextjs-webpack-quickjs 168 0 3
✅ nitro-node 141 0 30
✅ nitro-quickjs 141 0 30
✅ nuxt-node 141 0 30
✅ nuxt-quickjs 141 0 30
❌ python-node 4 62 105
✅ sveltekit-node 160 0 11
✅ sveltekit-quickjs 160 0 11
✅ tanstack-start-node 141 0 30
✅ tanstack-start-quickjs 141 0 30
✅ vite-node 141 0 30
✅ vite-quickjs 141 0 30

✅ 💻 Local Development

App Passed Failed Skipped
✅ astro-stable-node 142 0 29
✅ astro-stable-quickjs 142 0 29
✅ express-stable-node 142 0 29
✅ express-stable-quickjs 142 0 29
✅ fastify-stable-node 142 0 29
✅ fastify-stable-quickjs 142 0 29
✅ hono-stable-node 142 0 29
✅ hono-stable-quickjs 142 0 29
✅ nest-stable-node 142 0 29
✅ nest-stable-quickjs 142 0 29
✅ nextjs-turbopack-canary-node 170 0 1
✅ nextjs-turbopack-canary-quickjs 170 0 1
✅ nextjs-turbopack-stable-node 170 0 1
✅ nextjs-turbopack-stable-quickjs 170 0 1
✅ nextjs-webpack-canary-node 170 0 1
✅ nextjs-webpack-canary-quickjs 170 0 1
✅ nextjs-webpack-stable-node 170 0 1
✅ nextjs-webpack-stable-quickjs 170 0 1
✅ nitro-stable-node 142 0 29
✅ nitro-stable-quickjs 142 0 29
✅ nuxt-stable-node 142 0 29
✅ nuxt-stable-quickjs 142 0 29
✅ sveltekit-stable-node 161 0 10
✅ sveltekit-stable-quickjs 161 0 10
✅ tanstack-start-node 142 0 29
✅ tanstack-start-quickjs 142 0 29
✅ vite-stable-node 142 0 29
✅ vite-stable-quickjs 142 0 29

✅ 📦 Local Production

App Passed Failed Skipped
✅ astro-stable-node 142 0 29
✅ astro-stable-quickjs 142 0 29
✅ express-stable-node 142 0 29
✅ express-stable-quickjs 142 0 29
✅ fastify-stable-node 142 0 29
✅ fastify-stable-quickjs 142 0 29
✅ hono-stable-node 142 0 29
✅ hono-stable-quickjs 142 0 29
✅ nest-stable-node 142 0 29
✅ nest-stable-quickjs 142 0 29
✅ nextjs-turbopack-canary-node 170 0 1
✅ nextjs-turbopack-canary-quickjs 170 0 1
✅ nextjs-turbopack-stable-node 170 0 1
✅ nextjs-turbopack-stable-quickjs 170 0 1
✅ nextjs-webpack-canary-node 170 0 1
✅ nextjs-webpack-canary-quickjs 170 0 1
✅ nextjs-webpack-stable-node 170 0 1
✅ nextjs-webpack-stable-quickjs 170 0 1
✅ nitro-stable-node 142 0 29
✅ nitro-stable-quickjs 142 0 29
✅ nuxt-stable-node 142 0 29
✅ nuxt-stable-quickjs 142 0 29
✅ sveltekit-stable-node 161 0 10
✅ sveltekit-stable-quickjs 161 0 10
✅ tanstack-start-node 142 0 29
✅ tanstack-start-quickjs 142 0 29
✅ vite-stable-node 142 0 29
✅ vite-stable-quickjs 142 0 29

✅ 🐘 Local Postgres

App Passed Failed Skipped
✅ astro-stable-node 142 0 29
✅ astro-stable-quickjs 142 0 29
✅ express-stable-node 142 0 29
✅ express-stable-quickjs 142 0 29
✅ fastify-stable-node 142 0 29
✅ fastify-stable-quickjs 142 0 29
✅ hono-stable-node 142 0 29
✅ hono-stable-quickjs 142 0 29
✅ nest-stable-node 142 0 29
✅ nest-stable-quickjs 142 0 29
✅ nextjs-turbopack-canary-node 170 0 1
✅ nextjs-turbopack-canary-quickjs 170 0 1
✅ nextjs-turbopack-stable-node 170 0 1
✅ nextjs-turbopack-stable-quickjs 170 0 1
✅ nextjs-webpack-canary-node 170 0 1
✅ nextjs-webpack-canary-quickjs 170 0 1
✅ nextjs-webpack-stable-node 170 0 1
✅ nextjs-webpack-stable-quickjs 170 0 1
✅ nitro-stable-node 142 0 29
✅ nitro-stable-quickjs 142 0 29
✅ nuxt-stable-node 142 0 29
✅ nuxt-stable-quickjs 142 0 29
✅ sveltekit-stable-node 161 0 10
✅ sveltekit-stable-quickjs 161 0 10
✅ tanstack-start-node 142 0 29
✅ tanstack-start-quickjs 142 0 29
✅ vite-stable-node 142 0 29
✅ vite-stable-quickjs 142 0 29

✅ 🪟 Windows

App Passed Failed Skipped
✅ nextjs-turbopack-node 170 0 1
✅ nextjs-turbopack-quickjs 170 0 1

✅ vercel-http-transport

App Passed Failed Skipped
✅ example 141 0 30
✅ express 141 0 30
✅ hono 141 0 30
✅ nextjs-turbopack 168 0 3
✅ nitro 141 0 30
✅ vite 141 0 30

✅ vercel-multi-region

App Passed Failed Skipped
✅ nextjs-turbopack 27 0 0

✅ vercel-ws-transport

App Passed Failed Skipped
✅ example 141 0 30
✅ express 141 0 30
✅ nextjs-turbopack 168 0 3
✅ vite 141 0 30

📋 View full workflow run

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

commit 03e6e67 · Fri, 25 Sep 2026 04:09:12 GMT · run logs

Backend: vercel · app: nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 2078 (+46%) 🔻 2497 🔴 (+37%) 🔻 2570 🔴 (+37%) 🔻 2708 🔴 (+36%) 🔻 30
TTFS stream 178 (-20%) 💚 2431 🔴 (+17%) 🔻 2459 🔴 (+16%) 🔻 2487 🔴 (+7.8%) 30
TTFS hook + stream 538 (+45%) 🔻 2704 🔴 (+35%) 🔻 2767 🔴 (+32%) 🔻 2922 🔴 (+26%) 🔻 30
Fan-out TTFS Promise.all(100 steps) 494 (-1.2%) 749 (+0.8%) 914 (+22%) 🔻 2617 (+22%) 🔻 10
Fan-out TTLS Promise.all(100 steps) 2945 (+39%) 🔻 4181 (-17%) 💚 5583 (-5.5%) 10027 (+26%) 🔻 10
STSO 1020 steps (inline) 96 (-19%) 💚 140 (-11%) 159 (-12%) 248 (-31%) 💚 1019
WO 1020 steps 140715 (-10%) 140715 (-10%) 140715 (-10%) 140715 (-10%) 1
CRTT first chunk (pooled) 63 (-17%) 💚 96 (-19%) 💚 126 (-11%) 185 (-21%) 💚 28

Streams

Scenario CRTT 1st p75 p90 p99 CDV max iters
paced control (100/s, 60B) 83.5 (-22%) 118 (-45%) 138 (-66%) 196 (-83%) 89 (-66%) 10
size sweep (100/s, 160B-12KB) 91.5 (-18%) 122 (-35%) 159 (-38%) 269 (-37%) 98 (-38%) 10
replay gateway-gpt-5.4-nano-2000t (1x) 81 (-28%) 118 (-38%) 134 (-47%) 366 (-39%) 354 (+9%) 3
replay eve-gpt-5.6-sol-2000t (1x) 97.5 (-16%) 125 (-31%) 154 (-33%) 216 (-45%) 269 (-26%) 2
replay eve-gpt-5.6-sol-2000t (2x) 81 (-30%) 821 (+244%) 2284 (+637%) 4307 (+772%) 406 (+10%) 3
📈 STSO distribution vs main (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: main 156783ms → this run 140582ms (Δ -16201ms, -10%)

 50-100 ms  ┃                         main   0  this   1    +1
100-150 ms  ███████████████████░░░░┃  main 675  this 863  +188
150-200 ms  ██┃█████                  main 283  this 123  -160
200-250 ms  ┃                         main  27  this  22    -5
250-300 ms  ┃                         main  12  this   5    -7
300-350 ms  ┃                         main  11  this   2    -9
350-400 ms  ┃                         main   3  this   0    -3
400-450 ms  ┃                         main   0  this   2    +2
450-500 ms  ┃                         main   3  this   1    -2
500-550 ms  ┃                         main   2  this   0    -2
550-600 ms  ┃                         main   1  this   0    -1
600-650 ms  ┃                         main   1  this   0    -1
700-750 ms  ┃                         main   1  this   0    -1
📈 CRTT drill-down vs main (RTT distributions & profiles)
variant  RTT 1ms→5s+             avg         p50           p90           p99     n
control  ······██·····  101.4 (-44%)  100 (-31%)    138 (-66%)    196 (-83%)  3000
sweep    ······██▁····  104.5 (-32%)   99 (-32%)    159 (-38%)    269 (-37%)  3000
gw 1x    ·····▁██▁····  105.3 (-33%)  101 (-27%)    134 (-47%)    366 (-39%)  5295
eve 1x   ·····▁▇█▁····  106.2 (-32%)  100 (-29%)    154 (-33%)    216 (-45%)  5186
eve 2x   ·····▁▄█▂▂▁▁·    347 (+79%)  116 (-33%)  2284 (+637%)  4307 (+772%)  7779

RTT over stream progress (avg per tenth of stream, bars scaled min→max):

control  █▅▅▃▁▄▂▂▁▄  97–111ms
sweep    █▂▄▄▃▁▃▂▅▂  99–116ms
gw 1x    █▃▁▁▂▄▅▂▂▄  97–124ms
eve 1x   █▁▂▄▄▃█▇▃▂  100–115ms
eve 2x   ▂█▃▂▁▁▁▁▂▁  164–1101ms

RTT by chunk size (avg per log size bin, ~160B → ~12KB serialized, bars scaled min→max):

sweep  ▆█▇▅▃▁▁  101–109ms

Delivery jitter over stream progress (avg positive CDV per tenth of stream, bars scaled min→max):

control  ▁▅▃▁█▅▇▃▁▃  29–33ms
sweep    ▁▂▃▄▁█▄▅▅▃  38–48ms
gw 1x    █▂▄▁▄█▄▄▁▆  27–36ms
eve 1x   ▅▆▁▅▆▄█▅▅▄  19–25ms
eve 2x   █▁▁▄▄▃▃▂▄▄  18–48ms
ℹ️ Metric definitions & methodology

Streams: first-chunk RTT (the stream-open path, before any buffering/backpressure), CRTT percentiles, and worst delivery stall (CDV max). Cells are medians across iterations; per-run values in the artifacts. No 🔴/🟢 marks until targets attach.

The collapsed STSO distribution section above buckets every step gap, split inline (same warm process — pure framework overhead) vs queue-hop (fresh process — dispatch, reinit, replay). █ = main, ┃ = this run, ░ = fill.

The collapsed CRTT drill-down: per-variant RTT histograms (fixed log bins, · = empty) and mean RTT/positive-CDV profile lines over stream progress and chunk size. Histograms, avgs, and profiles merge exactly across runs; p50–p99 are percentile-of-percentiles. Per-index rows live in the artifacts.

Best/P75/P90/P99 deltas compare against the most recent benchmark run on main at the time of this run. 🔻 flags a delta worse than +15%, 💚 one better than −15%.

Metrics — TTFS: time to first step body (in-deployment start() → first step body) · Fan-out TTFS: fan-out time to first step (in-deployment start() → first of the parallel step bodies to complete) · Fan-out TTLS: fan-out time to last step (in-deployment start() → last of the parallel step bodies to complete, i.e. when the Promise.all resolves) · STSO: step-to-step overhead (gap between consecutive step bodies) · WO: workflow overhead (whole-run time outside step bodies, in-deployment anchored) · CRTT: chunk round-trip time (per-chunk write → read latency, one clock domain: deployment → stream backend → same deployment) · CDV: chunk delay variation / delivery jitter (inter-arrival gap minus inter-write gap per seq-adjacent pair; skew-free; the row is each run's MAX positive value, so one stall moves it)

Scenarios — step: one trivial no-op step, no stream; no hooks, so the run stays in turbo mode (in-process fast path) · stream: one streaming step; no hooks, so the run stays in turbo mode (in-process fast path) · hook + stream: registers a hook before one step, which exits turbo mode (dispatch path) · 1020 steps: 1020 trivial sequential steps; STSO is measured between consecutive steps in the given step ranges, and WO is the whole-run overhead outside step bodies · Promise.all(100 steps): 100 trivial no-op steps started together in a single Promise.all; Fan-out TTFS is the first of them to complete and Fan-out TTLS the last, both from the in-deployment clientStart, so their gap is the spread the runtime adds across the fan-out · paced control (100/s, 60B): the control: 300 tiny (~60B) deltas metronome-paced at 100/s — zero workload structure, so it reads the transport floor and flush cadence, and disambiguates transport-wide vs workload-specific when a replay row moves · size sweep (100/s, 160B-12KB): same pacing as the control with deltas padded in rotation across seven log-spaced sizes (~160B–12KB) — rotation decouples size from stream position, so it isolates whether chunk size causes latency · replay gateway-gpt-5.4-nano-2000t (1x): raw provider SSE cadence captured at the AI gateway boundary (gpt-5.4-nano, the most popular gateway model; per-token deltas p50 208B = the modal production chunk size), replayed exactly as measured — the typical customer's workload; its CDV is the typical customer's real delivery jitter · replay eve-gpt-5.6-sol-2000t (1x): a captured eve turn (gpt-5.6-sol, the most-used demanding eve model; ~2000 output tokens = production p50 turn length) replayed exactly as measured — eve's envelope protocol re-ships the cumulative message so sizes ramp 142B→13KB; the demanding outlier tenant's reality · replay eve-gpt-5.6-sol-2000t (2x): the same eve capture at 2x — the headroom/stress row; real fast-tier models emit the same chunk sizes at proportionally higher rate, so time compression is a faithful speed model · first chunk (pooled): every run's seq-0 RTT pooled across all stream scenarios — the first chunk precedes any workload differentiation, so pooling samples one shared stream-open path with exact percentiles

Replay cadences (semantic sha256) — eve-gpt-5.6-sol-2000t eaf22f5946e7c61f3c65c7006d550df180cfabd4e706254a09f22aec0cfb420d · gateway-gpt-5.4-nano-2000t 6f24ac518b6b83ff1d0e85a5fe78230db192716d66a7fc6b2fe022752001d041

🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600

All timestamps are deployment-side; runs are triggered in-deployment, so the CI runner and api.vercel.com sit outside every measured window. TTFS = start() → first step body (includes dispatch + any cold start); Fan-out TTFS/TTLS = first/last step completion of one Promise.all from the same anchor (the gap is the runtime’s fan-out spread); STSO/WO between step bodies; CRTT inside the workflow (excludes the api.vercel.com read path).

Cold starts stay in the numbers (real bursty-workload latency, inflates P75+); Best is the warm floor.

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Sim World

Simulated world deterministic testing for races. Traces

🟠 world-sim scenario book — 1 fail of 42 total

fence=per-spec

scenario outcome events virt replay violations
✅ smoke-no-steps completed 3 0ms ok 0
✅ smoke-one-step completed 6 0ms ok 0
✅ hook-at-step-started completed 12 0ms ok 0
✅ hook-at-step-completed completed 12 0ms ok 0
✅ hook-at-hook-created completed 12 0ms ok 0
✅ deadline-hook-wins completed 7 1.0h ok 0
✅ deadline-expires completed 7 1.0h ok 0
✅ step-vs-timer-early-settlement completed 8 1.0h ok 0
✅ long-sleep completed 11 30.0d ok 0
✅ hook-never-arrives stalled 3 0ms skipped 0
✅ step-retries-twice completed 10 2.0s ok 0
✅ parallel-steps completed 9 0ms ok 0
✅ hook-on-execution-state completed 12 0ms ok 0
✅ peek-hook-before-branch completed 12 0ms ok 0
✅ peek-hook-after-branch completed 12 0ms ok 0
✅ peek-hook-at-registration completed 12 0ms ok 0
✅ race-hook-before-probe completed 12 0ms ok 0
✅ race-hook-after-probe completed 12 0ms ok 0
✅ race-duplicate-delivery completed 13 0ms ok 0
✅ attr-hook-before-step completed 11 0ms ok 0
✅ attr-hook-after-step completed 11 0ms ok 0
✅ attr-from-step-body completed 13 0ms ok 0
✅ fork-hook-after-timeout completed 14 1.0m ok 0
✅ fork-hook-before-timeout completed 14 1.0m ok 0
✅ count-hook-after-timeout completed 17 1.0m ok 0
✅ count-hook-before-timeout completed 20 1.0m ok 0
✅ stale-read-step-count-fork completed 20 1.0m ok 0
✅ stale-read-equal-step-counts completed 14 1.0m ok 0
✅ step-vs-step-fork completed 12 0ms ok 0
✅ step-vs-step-fork-fenced completed 12 0ms ok 0
✅ fence-catches-benign-direction completed 12 5ms ok 0
✅ in-flight-before-decision completed 17 1.0m ok 0
❌ in-flight-before-decision-counted completed 17 1.0m ok 0
✅ in-flight-after-decision completed 19 2.0m ok 0
✅ stale-read-step-count-fork-fenced completed 20 1.0m ok 0
✅ fork-hook-wins completed 13 1.0m ok 0
✅ fork-timeout-wins completed 13 1.0m ok 0
✅ unclaimed-payload-under-fork completed 17 1.0m ok 0
✅ claimed-payload-under-fork completed 17 1.0m ok 0
✅ writers-independent-step-bodies completed 12 0ms ok 0
✅ writers-scripted-tempo completed 12 0ms ok 0
✅ cancel-mid-step cancelled 7 0ms skipped 0

Full trace: world-sim.txt

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor
Framework Flow route Step reg. Framework output
hono 252.0 KiB (±0) 94.9 KiB (+39 B) 1.91 MiB (+82 B)
nextjs-turbopack 259.3 KiB (±0) 426 B (±0) 908.3 KiB (+78 B)
About these numbers

Sizes are gzip; parentheses show the change against main.
Flow route and Step reg. gate this job, on raw bytes rather than the gzip shown, at max(2%, 50.0 KiB). Framework output is informational.

03e6e67 · run

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated
example-nextjs-workflow-turbopack Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
example-nextjs-workflow-webpack Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
example-workflow Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
workbench-astro-workflow Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
workbench-express-workflow Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
workbench-fastify-workflow Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
workbench-hono-workflow Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
workbench-nestjs-workflow Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
workbench-nitro-workflow Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
workbench-nuxt-workflow Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
workbench-python-workflow Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
workbench-sveltekit-workflow Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
workbench-tanstack-start-workflow Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
workbench-vite-workflow Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
workflow-docs Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
workflow-swc-playground Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
workflow-tarballs Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC
workflow-web Ready Ready Preview, v0 Sep 25, 2026 3:53am UTC

@vercel
vercel Bot temporarily deployed to Preview – workflow-docs September 24, 2026 17:34 Inactive
VaguelySerious added a commit that referenced this pull request Sep 24, 2026
…mped spec version

Address review on #4327:

- Raise the capability probe budget from 2s to 10s. healthCheck() returns
  on the first answer, so only a miss pays; a cold but healthy older-major
  target (e.g. stable, spec 3) is now read from the probe instead of being
  stamped with the spec-6 guess it would reject.
- Be honest about the miss floor: spec 6 is the lowest a v5 runtime
  executes, and is not safe for older-major targets. Log a once-per-process
  warning on a miss, and TODO lowering the floor once #4366 and
  workflow-server#1044 ship.
- Record workflow.run.spec_version / spec_version_source and the probe's
  latency or error on the start() span.
- Cap `wf inspect` replay's probed spec version at the CLI World's.
- Tests: cold spec-3 target, end-to-end probe timeout, 'latest' to another
  deployment and to self, retention error naming the target, compression
  off below spec 5, explicit-version attribution.
- Docs and changeset updates.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Co-Authored-By: Peter Wielander <29887157+VaguelySerious@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
run_started sent mintedSpecVersion() per request, which re-reads
WORKFLOW_SEALED_LOG, while the runtime validated the value createWorld
captured. Flipping the env in-process could make an executor attest a
version it was never validated for, and the backend would raise runs to it.
createWorld now reads it once and threads it to event writes (internal
APIConfig.mintedSpecVersion); direct callers without it fall back to a fresh
read. From the formal-model review on #4406 (E1).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Co-Authored-By: Peter Wielander <29887157+VaguelySerious@users.noreply.github.com>
export const CAPABILITY_ONLY_SPEC_VERSIONS: ReadonlySet<number> = new Set([
SPEC_VERSION_SUPPORTS_CBOR_QUEUE_TRANSPORT,
SPEC_VERSION_SUPPORTS_ATTRIBUTES,
SPEC_VERSION_SUPPORTS_COMPRESSION,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Non-blocking, doc only. The definition says a capability-only version affects only "the runtime executing it". Compression (5) also affects anyone else who reads the run's payloads: a caller awaiting returnValue, a parent run, or an older dashboard/CLI. It can't be reached today, because only v5 executors attest their version and they're at 6 or above, so raising a run past 5 always crosses slot identity and needs a fresh log. Could the doc say that "capability-only" also means "no reader outside the executor is affected"? Then whoever classifies the next version checks that too.


Local agent review (`vercel-ai-gateway/anthropic/claude-opus-5.5`)

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

Approving. The attested version is now the World's declared specVersion, fixed when the World is created, and it is sent only on run_started. The capability/structural classification treats any unclassified version as structural, and the test that enumerates every SPEC_VERSION_SUPPORTS_* constant guards that. world 21 and world-vercel events 65 pass locally. The two failing CI jobs (Python Conformance and python-node) are also cancelled on every recent main run. Shipping this before workflow-server#1044 is safe: older servers ignore the unknown meta key.

Copy link
Copy Markdown
Contributor

Qualifying my approval note: this is safe to ship before vercel/workflow-server#1044 only while #1044 is not partly rolled out with run-spec-version-upgrade on. If the server goes out with the flag on from day one, release this after the server rollout has finished in every region. See https://github.com/vercel/workflow-server/pull/1044 for the rollback caveat.

@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 54b48ef (AI decision).

The change depends entirely on main-only APIs: origin/stable's packages/world/src/spec-version.ts stops at spec version 3 and has no mintedSpecVersion(), slot identity, or sealed log, so neither the executorSpecVersion attestation nor the new capability-only/structural classification has anything to attach to there. It is also a fix that by construction only the newer executor can apply — the PR itself notes no v4 release can repair these runs — and it adds new exported API surface (CAPABILITY_ONLY_SPEC_VERSIONS, STRUCTURAL_SPEC_VERSIONS, crossesStructuralSpecVersion) plus a new protocol field.

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

54b48ef4deffb8333de5d8c1dff8e8ad8a791efd

This branch was successfully deployed

18 active deployments
Preview – workflow-swc-playground — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – workflow-docs — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – example-nextjs-workflow-webpack — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – workbench-nuxt-workflow — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – example-nextjs-workflow-turbopack — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – workbench-express-workflow — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – workbench-tanstack-start-workflow — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – workbench-sveltekit-workflow — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – workflow-tarballs — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – workbench-vite-workflow — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – workbench-astro-workflow — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – example-workflow — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – workbench-fastify-workflow — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – workbench-hono-workflow — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – workbench-nitro-workflow — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – workbench-nestjs-workflow — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – workflow-web — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Preview – workbench-python-workflow — 03e6e677 Deployed Sep 25, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants