Skip to content

[world-vercel] Call the v5 event API, drop pre-slot event-id support - #3512

Draft
VaguelySerious wants to merge 6 commits into
mainfrom
peter/world-vercel-v5
Draft

[world-vercel] Call the v5 event API, drop pre-slot event-id support#3512
VaguelySerious wants to merge 6 commits into
mainfrom
peter/world-vercel-v5

Conversation

@VaguelySerious

@VaguelySerious VaguelySerious commented Aug 12, 2026

Copy link
Copy Markdown
Member
  1. world-vercel calls the v5 event API
  • Step and Wait rows stop being written by the event path, so a v5 run reads back with no steps through steps.list, wf inspect, and the dashboard until the backend's read model is rebuilt from events.
  • runs.cancel stays on /v4/runs/cancel
  • WORKFLOW_EVENTS_TRANSPORT=ws (opt-in, off by default) forwards into v5 too, on the backend side: the socket hands each frame to the same events route the HTTP branch posts to, so a run gets the same write semantics over either transport. The WS endpoint stays versioned on its own (/websockets/v1), which is about the frame envelope rather than which write path a frame reaches
  • WORKFLOW_SERVER_URL_OVERRIDE is pinned to the branch serving /v5 for the life of this PR. Must be reverted to '' before merge.

Then three defects the first Vercel Prod run exposed. All 24 prod lanes failed the same ~14 tests while every Local Prod and Local Postgres lane was green on the same commit, so only the v5 route pin was implicated. Fixes:

  1. The writer states its attempt on step_started
  • v4's start patch did add({attempt: 1}) on the step row and the runtime sized its retry budget from the attempt the response returned. There is no row on v5, so the World reads eventData.attempt, which nothing sent
  • every attempt therefore reported 1, no step ever exhausted its budget, and any failing step retried forever. That accounts for every timeout in the run
  • the writer already knows the number, since it is what the pre-body retry ceiling is enforced against. Omitted when the caller supplied none, so a World that counts for itself is not overwritten by a guess
  1. Step assertions read the analytics listing instead of the storage one
  • attempt added to the analytics steps listing server-side (startedCount AS attempt, the same number the row's counter held by construction), so the two retry tests no longer need --withData to force the storage path
  • a step's error is a payload and no metadata listing carries one, so the two error tests read the step_failed event, which holds it on every World
  • these four cannot go green while the URL override is in place: a preview workflow-server emits no usage facts, so analytics is empty for every run in these lanes. Labs preview ingest drops to exactly zero the minute the pinned run starts while every other tenant keeps flowing. All four pass locally against nextjs-turbopack
  • not fixed here: on v5 an analytics lag makes wf inspect steps report an empty step list rather than "cannot answer", because the fallback it drops to no longer holds steps
  1. A step event written behind the step's result is a straggler, not divergence
  • a queue-dispatched step has no claim to lose: its message can be delivered twice, both deliveries start a step that already exists, and neither attempt is wrong. When the loser's body fails behind the winner's result, the step_retrying it records repeats no class, so no consumer wants it and the replay was failing a finished run with CORRUPTED_EVENT_LOG
  • measured rather than argued: with the backend's inline claim fenced and this reverted, event-log-race-repro corrupts 12 of 12 storm runs; with it, 0
  • the backend half (separate PR) fixes the other defect the storms exposed: stepCreated is the SDK's exactly-once inline-ownership signal, and without a step row nothing held the claim, so every racing writer was told it created the step
  • changes one deliberate expectation: a start behind a completion that repeats no class used to be divergence
  1. The race repro prints the log it corrupted on
  • it reported CORRUPTED_EVENT_LOG and nothing else. The message the runtime failed on does not survive into the run row, and the observability tables are not populated for a preview backend, so which two writes collided had to be inferred from the shape of the code — which is how two diagnoses here went wrong
  • a failed or stuck attempt now carries its event-log tail

Known red

event-log-race-repro still fails, and not on corruption. All 12 storm runs sit at running for the full 240s with 4300-4600 events whose tail is nothing but step_started/step_retrying cycling over ~15 correlation ids: an infinite retry loop where the ceiling never fires. Two of the twelve already did this before any fix.

It is v5-only for a structural reason: there the attempt is whatever the client puts on the event, while every world that materializes a step row computes it from its own counter and ignores the client — so world-postgres is 14/14 clean on identical code and cannot exercise this at all. Not diagnosed further.

@changeset-bot

changeset-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9af6f63

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

This PR includes changesets to release 20 packages
Name Type
@workflow/world Patch
workflow Patch
@workflow/core Patch
@workflow/world-vercel Patch
@workflow/cli Patch
@workflow/vitest Patch
@workflow/web-shared Patch
@workflow/web Patch
@workflow/world-local Patch
@workflow/world-postgres Patch
@workflow/world-testing 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

@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview Aug 13, 2026 2:23am
example-nextjs-workflow-webpack Ready Ready Preview Aug 13, 2026 2:23am
example-workflow Ready Ready Preview Aug 13, 2026 2:23am
workbench-astro-workflow Ready Ready Preview Aug 13, 2026 2:23am
workbench-express-workflow Ready Ready Preview Aug 13, 2026 2:23am
workbench-fastify-workflow Ready Ready Preview Aug 13, 2026 2:23am
workbench-hono-workflow Ready Ready Preview Aug 13, 2026 2:23am
workbench-nestjs-workflow Ready Ready Preview Aug 13, 2026 2:23am
workbench-nitro-workflow Ready Ready Preview Aug 13, 2026 2:23am
workbench-nuxt-workflow Ready Ready Preview Aug 13, 2026 2:23am
workbench-python-workflow Error Error Aug 13, 2026 2:23am
workbench-sveltekit-workflow Ready Ready Preview Aug 13, 2026 2:23am
workbench-tanstack-start-workflow Ready Ready Preview Aug 13, 2026 2:23am
workbench-vite-workflow Ready Ready Preview Aug 13, 2026 2:23am
workflow-docs Ready Ready Preview, v0 Aug 13, 2026 2:23am
workflow-swc-playground Ready Ready Preview Aug 13, 2026 2:23am
workflow-tarballs Ready Ready Preview Aug 13, 2026 2:23am
workflow-web Ready Ready Preview Aug 13, 2026 2:23am

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

Some tests failed

❌ Failed E2E Tests

▲ Vercel Production (161 failed)

astro-node (7 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step

astro-quickjs (6 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

example-node (7 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step

example-quickjs (6 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

express-node (7 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step

express-quickjs (6 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

fastify-node (6 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

fastify-quickjs (6 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

hono-node (7 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortAnyInStepWorkflow: AbortSignal.any inside a step composes deserialized signals
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

hono-quickjs (6 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

nest-node (6 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

nest-quickjs (6 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

nextjs-turbopack-node (7 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step

nextjs-webpack-node (7 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortAnyInStepWorkflow: AbortSignal.any inside a step composes deserialized signals
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

nextjs-webpack-quickjs (6 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

nitro-node (7 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortAnyInStepWorkflow: AbortSignal.any inside a step composes deserialized signals
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

nitro-quickjs (6 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

nuxt-node (7 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortAnyInStepWorkflow: AbortSignal.any inside a step composes deserialized signals
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

nuxt-quickjs (6 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

sveltekit-node (6 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

sveltekit-quickjs (6 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

tanstack-start-node (8 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortAnyInStepWorkflow: AbortSignal.any inside a step composes deserialized signals
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step

tanstack-start-quickjs (6 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

vite-node (7 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortAnyInStepWorkflow: AbortSignal.any inside a step composes deserialized signals
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

vite-quickjs (6 failed):

  • 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 not registered StepNotRegisteredError fails the step but workflow can catch it
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZWFRGXK0GQA9D9A6P12BE9H | 🔍 observability
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

E2E Test Summary

Summary
Passed Failed Skipped Total
❌ ▲ Vercel Production 3152 161 587 3900
✅ 💻 Local Development 3810 0 558 4368
✅ 📦 Local Production 3810 0 558 4368
✅ 🐘 Local Postgres 3810 0 558 4368
✅ 🪟 Windows 312 0 0 312
✅ vercel-multi-region 27 0 0 27
Total 14921 161 2261 17343
Details by Category

❌ ▲ Vercel Production

App Passed Failed Skipped
❌ astro-node 121 7 28
❌ astro-quickjs 122 6 28
❌ example-node 121 7 28
❌ example-quickjs 122 6 28
❌ express-node 121 7 28
❌ express-quickjs 122 6 28
❌ fastify-node 122 6 28
❌ fastify-quickjs 122 6 28
❌ hono-node 121 7 28
❌ hono-quickjs 122 6 28
❌ nest-node 122 6 28
❌ nest-quickjs 122 6 28
❌ nextjs-turbopack-node 146 7 3
❌ nextjs-webpack-node 146 7 3
❌ nextjs-webpack-quickjs 147 6 3
❌ nitro-node 121 7 28
❌ nitro-quickjs 122 6 28
❌ nuxt-node 121 7 28
❌ nuxt-quickjs 122 6 28
❌ sveltekit-node 141 6 9
❌ sveltekit-quickjs 141 6 9
❌ tanstack-start-node 120 8 28
❌ tanstack-start-quickjs 122 6 28
❌ vite-node 121 7 28
❌ vite-quickjs 122 6 28

✅ 💻 Local Development

App Passed Failed Skipped
✅ astro-stable-node 130 0 26
✅ astro-stable-quickjs 130 0 26
✅ express-stable-node 130 0 26
✅ express-stable-quickjs 130 0 26
✅ fastify-stable-node 130 0 26
✅ fastify-stable-quickjs 130 0 26
✅ hono-stable-node 130 0 26
✅ hono-stable-quickjs 130 0 26
✅ nest-stable-node 130 0 26
✅ nest-stable-quickjs 130 0 26
✅ nextjs-turbopack-canary-node 137 0 19
✅ nextjs-turbopack-canary-quickjs 137 0 19
✅ nextjs-turbopack-stable-node 156 0 0
✅ nextjs-turbopack-stable-quickjs 156 0 0
✅ nextjs-webpack-canary-node 137 0 19
✅ nextjs-webpack-canary-quickjs 137 0 19
✅ nextjs-webpack-stable-node 156 0 0
✅ nextjs-webpack-stable-quickjs 156 0 0
✅ nitro-stable-node 130 0 26
✅ nitro-stable-quickjs 130 0 26
✅ nuxt-stable-node 130 0 26
✅ nuxt-stable-quickjs 130 0 26
✅ sveltekit-stable-node 149 0 7
✅ sveltekit-stable-quickjs 149 0 7
✅ tanstack-start-node 130 0 26
✅ tanstack-start-quickjs 130 0 26
✅ vite-stable-node 130 0 26
✅ vite-stable-quickjs 130 0 26

✅ 📦 Local Production

App Passed Failed Skipped
✅ astro-stable-node 130 0 26
✅ astro-stable-quickjs 130 0 26
✅ express-stable-node 130 0 26
✅ express-stable-quickjs 130 0 26
✅ fastify-stable-node 130 0 26
✅ fastify-stable-quickjs 130 0 26
✅ hono-stable-node 130 0 26
✅ hono-stable-quickjs 130 0 26
✅ nest-stable-node 130 0 26
✅ nest-stable-quickjs 130 0 26
✅ nextjs-turbopack-canary-node 137 0 19
✅ nextjs-turbopack-canary-quickjs 137 0 19
✅ nextjs-turbopack-stable-node 156 0 0
✅ nextjs-turbopack-stable-quickjs 156 0 0
✅ nextjs-webpack-canary-node 137 0 19
✅ nextjs-webpack-canary-quickjs 137 0 19
✅ nextjs-webpack-stable-node 156 0 0
✅ nextjs-webpack-stable-quickjs 156 0 0
✅ nitro-stable-node 130 0 26
✅ nitro-stable-quickjs 130 0 26
✅ nuxt-stable-node 130 0 26
✅ nuxt-stable-quickjs 130 0 26
✅ sveltekit-stable-node 149 0 7
✅ sveltekit-stable-quickjs 149 0 7
✅ tanstack-start-node 130 0 26
✅ tanstack-start-quickjs 130 0 26
✅ vite-stable-node 130 0 26
✅ vite-stable-quickjs 130 0 26

✅ 🐘 Local Postgres

App Passed Failed Skipped
✅ astro-stable-node 130 0 26
✅ astro-stable-quickjs 130 0 26
✅ express-stable-node 130 0 26
✅ express-stable-quickjs 130 0 26
✅ fastify-stable-node 130 0 26
✅ fastify-stable-quickjs 130 0 26
✅ hono-stable-node 130 0 26
✅ hono-stable-quickjs 130 0 26
✅ nest-stable-node 130 0 26
✅ nest-stable-quickjs 130 0 26
✅ nextjs-turbopack-canary-node 137 0 19
✅ nextjs-turbopack-canary-quickjs 137 0 19
✅ nextjs-turbopack-stable-node 156 0 0
✅ nextjs-turbopack-stable-quickjs 156 0 0
✅ nextjs-webpack-canary-node 137 0 19
✅ nextjs-webpack-canary-quickjs 137 0 19
✅ nextjs-webpack-stable-node 156 0 0
✅ nextjs-webpack-stable-quickjs 156 0 0
✅ nitro-stable-node 130 0 26
✅ nitro-stable-quickjs 130 0 26
✅ nuxt-stable-node 130 0 26
✅ nuxt-stable-quickjs 130 0 26
✅ sveltekit-stable-node 149 0 7
✅ sveltekit-stable-quickjs 149 0 7
✅ tanstack-start-node 130 0 26
✅ tanstack-start-quickjs 130 0 26
✅ vite-stable-node 130 0 26
✅ vite-stable-quickjs 130 0 26

✅ 🪟 Windows

App Passed Failed Skipped
✅ nextjs-turbopack-node 156 0 0
✅ nextjs-turbopack-quickjs 156 0 0

✅ vercel-multi-region

App Passed Failed Skipped
✅ nextjs-turbopack 27 0 0

📋 View full workflow run

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

commit 8ef177d · Thu, 13 Aug 2026 02:05:19 GMT · run logs

Backend: vercel · app: nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 230 (-4.6%) 334 🔴 (-71%) 💚 575 🔴 (-51%) 💚 1339 🔴 (+5.9%) 30
TTFS stream 221 (-19%) 💚 344 🔴 (-70%) 💚 412 🔴 (-66%) 💚 1385 🔴 (-12%) 30
TTFS hook + stream 358 (-11%) 534 🔴 (-62%) 💚 616 🔴 (-57%) 💚 1007 🔴 (-32%) 💚 30
STSO 1020 steps (inline) 110 (-2.7%) 184 (-13%) 214 (-15%) 💚 336 (-19%) 💚 1019
WO 1020 steps 179536 (-14%) 179536 (-14%) 179536 (-14%) 179536 (-14%) 1
SL stream latency 108 (+11%) 167 🔴 (+3.7%) 277 🔴 (+28%) 🔻 838 🔴 (+124%) 🔻 30
SO stream overhead (text) 149 (-0.7%) 245 (-14%) 273 (-55%) 💚 616 (-20%) 💚 30
SO stream overhead (structured) 140 (+6.1%) 420 🔴 (+58%) 🔻 469 (+29%) 🔻 576 (-98%) 💚 30
📈 STSO distribution vs main (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: main 207603ms → this run 179271ms (Δ -28332ms, -14%)

  100-150 ms  █░░░░░░░░░┃               main   6  this 280  +274
  150-200 ms  █████████████████████┃██  main 640  this 589   -51
  200-250 ms  ███┃██████                main 268  this 108  -160
  250-300 ms  ┃█                        main  61  this  28   -33
  300-350 ms  ┃                         main  25  this   5   -20
  350-400 ms  ┃                         main   6  this   4    -2
  400-450 ms  ┃                         main   5  this   0    -5
  450-500 ms  ┃                         main   3  this   0    -3
  500-550 ms  ┃                         main   2  this   2    +0
  550-600 ms  ┃                         main   1  this   1    +0
  600-650 ms  ┃                         main   1  this   0    -1
  850-900 ms  ┃                         main   0  this   1    +1
  900-950 ms  ┃                         main   1  this   0    -1
3400-3450 ms  ┃                         main   0  this   1    +1
📜 Previous results (6)

6a1fe97

Thu, 13 Aug 2026 01:39:05 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1327 (+451%) 🔻 1390 🔴 (+22%) 🔻 1404 🔴 (+20%) 🔻 1663 🔴 (+32%) 🔻 30
TTFS stream 1312 (+379%) 🔻 1365 🔴 (+17%) 🔻 1376 🔴 (+14%) 1469 🔴 (-6.3%) 30
TTFS hook + stream 1534 (+284%) 🔻 1668 🔴 (+20%) 🔻 1694 🔴 (+19%) 🔻 1730 🔴 (+16%) 🔻 30
STSO 1020 steps (inline) 98 (-13%) 133 (-37%) 💚 151 (-40%) 💚 238 (-43%) 💚 1019
WO 1020 steps 133512 (-36%) 💚 133512 (-36%) 💚 133512 (-36%) 💚 133512 (-36%) 💚 1
SL stream latency 78 (-20%) 💚 109 🔴 (-32%) 💚 114 🔴 (-47%) 💚 309 🔴 (-17%) 💚 30
SO stream overhead (text) 97 (-35%) 💚 149 (-48%) 💚 164 (-73%) 💚 245 (-68%) 💚 30
SO stream overhead (structured) 106 (-20%) 💚 150 (-43%) 💚 157 (-57%) 💚 187 (-99%) 💚 30

43270e6

Thu, 13 Aug 2026 01:10:01 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 314 (+30%) 🔻 1330 🔴 (+16%) 🔻 1374 🔴 (+18%) 🔻 1431 🔴 (+13%) 30
TTFS stream 1273 (+365%) 🔻 1318 🔴 (+13%) 1328 🔴 (+9.7%) 1370 🔴 (-13%) 30
TTFS hook + stream 1530 (+283%) 🔻 1633 🔴 (+17%) 🔻 1679 🔴 (+18%) 🔻 2014 🔴 (+35%) 🔻 30
STSO 1020 steps (inline) 99 (-12%) 141 (-33%) 💚 167 (-34%) 💚 283 (-32%) 💚 1019
WO 1020 steps 142959 (-32%) 💚 142959 (-32%) 💚 142959 (-32%) 💚 142959 (-32%) 💚 1
SL stream latency 79 (-19%) 💚 103 🔴 (-36%) 💚 113 🔴 (-48%) 💚 479 🔴 (+28%) 🔻 30
SO stream overhead (text) 103 (-31%) 💚 146 (-49%) 💚 166 (-73%) 💚 203 (-74%) 💚 30
SO stream overhead (structured) 108 (-18%) 💚 150 (-43%) 💚 171 (-53%) 💚 811 (-96%) 💚 30

6566c54

Thu, 13 Aug 2026 00:13:00 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1342 (+457%) 🔻 1466 🔴 (+28%) 🔻 1517 🔴 (+30%) 🔻 1592 🔴 (+26%) 🔻 30
TTFS stream 450 (+64%) 🔻 1398 🔴 (+20%) 🔻 1424 🔴 (+18%) 🔻 1750 🔴 (+12%) 30
TTFS hook + stream 1591 (+298%) 🔻 1758 🔴 (+26%) 🔻 1829 🔴 (+29%) 🔻 1911 🔴 (+28%) 🔻 30
STSO 1020 steps (inline) 105 (-7.1%) 159 (-25%) 💚 176 (-30%) 💚 276 (-33%) 💚 1019
WO 1020 steps 159471 (-24%) 💚 159471 (-24%) 💚 159471 (-24%) 💚 159471 (-24%) 💚 1
SL stream latency 114 (+18%) 🔻 169 🔴 (+5.0%) 234 🔴 (+8.3%) 341 🔴 (-8.8%) 30
SO stream overhead (text) 126 (-16%) 💚 178 (-37%) 💚 188 (-69%) 💚 414 (-47%) 💚 30
SO stream overhead (structured) 136 (+3.0%) 195 (-26%) 💚 208 (-43%) 💚 284 (-99%) 💚 30

621253b

Wed, 12 Aug 2026 23:43:54 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1180 (+390%) 🔻 1350 🔴 (+18%) 🔻 1378 🔴 (+18%) 🔻 1555 🔴 (+23%) 🔻 30
TTFS stream 185 (-32%) 💚 1227 🔴 (+5.3%) 1245 🔴 (+2.8%) 1344 🔴 (-14%) 30
TTFS hook + stream 1428 (+257%) 🔻 1611 🔴 (+16%) 🔻 1653 🔴 (+16%) 🔻 1863 🔴 (+25%) 🔻 30
STSO 1020 steps (inline) 105 (-7.1%) 142 (-33%) 💚 158 (-37%) 💚 268 (-35%) 💚 1019
WO 1020 steps 142843 (-32%) 💚 142843 (-32%) 💚 142843 (-32%) 💚 142843 (-32%) 💚 1
SL stream latency 86 (-11%) 116 🔴 (-28%) 💚 132 🔴 (-39%) 💚 152 🔴 (-59%) 💚 30
SO stream overhead (text) 100 (-33%) 💚 148 (-48%) 💚 159 (-74%) 💚 288 (-63%) 💚 30
SO stream overhead (structured) 114 (-14%) 169 (-36%) 💚 220 (-40%) 💚 331 (-99%) 💚 30

f6b469d

Wed, 12 Aug 2026 23:24:04 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 311 (+29%) 🔻 1362 🔴 (+19%) 🔻 1424 🔴 (+22%) 🔻 1503 🔴 (+19%) 🔻 30
TTFS stream 1284 (+369%) 🔻 1344 🔴 (+15%) 🔻 1384 🔴 (+14%) 1447 🔴 (-7.7%) 30
TTFS hook + stream 1522 (+281%) 🔻 1707 🔴 (+23%) 🔻 1746 🔴 (+23%) 🔻 1797 🔴 (+21%) 🔻 30
STSO 1020 steps (inline) 108 (-4.4%) 139 (-34%) 💚 155 (-38%) 💚 263 (-37%) 💚 1019
WO 1020 steps 141239 (-32%) 💚 141239 (-32%) 💚 141239 (-32%) 💚 141239 (-32%) 💚 1
SL stream latency 89 (-8.2%) 117 🔴 (-27%) 💚 159 🔴 (-26%) 💚 264 🔴 (-29%) 💚 30
SO stream overhead (text) 110 (-27%) 💚 172 (-39%) 💚 220 (-64%) 💚 1722 🔴 (+122%) 🔻 30
SO stream overhead (structured) 107 (-19%) 💚 174 (-34%) 💚 234 (-36%) 💚 854 (-96%) 💚 30

cc47927

Wed, 12 Aug 2026 22:04:37 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 421 (+75%) 🔻 1377 🔴 (+21%) 🔻 1392 🔴 (+19%) 🔻 1616 🔴 (+28%) 🔻 30
TTFS stream 1303 (+376%) 🔻 1364 🔴 (+17%) 🔻 1387 🔴 (+15%) 1413 🔴 (-9.9%) 30
TTFS hook + stream 1593 (+298%) 🔻 1674 🔴 (+20%) 🔻 1699 🔴 (+20%) 🔻 1780 🔴 (+20%) 🔻 30
STSO 1020 steps (inline) 105 (-7.1%) 146 (-31%) 💚 166 (-34%) 💚 245 (-41%) 💚 1019
WO 1020 steps 145228 (-30%) 💚 145228 (-30%) 💚 145228 (-30%) 💚 145228 (-30%) 💚 1
SL stream latency 97 (±0%) 118 🔴 (-27%) 💚 128 🔴 (-41%) 💚 184 🔴 (-51%) 💚 30
SO stream overhead (text) 116 (-23%) 💚 164 (-42%) 💚 186 (-70%) 💚 733 (-5.3%) 30
SO stream overhead (structured) 115 (-13%) 155 (-42%) 💚 177 (-51%) 💚 201 (-99%) 💚 30
ℹ️ Metric definitions & methodology

The collapsed STSO distribution section above buckets every step gap of the sequential-steps run (not a sampled window), split by whether the step ending the gap ran inline — in the same warm process as the step before it, so the gap is pure framework overhead — or after a queue-hop — the first step of a fresh process, which pays queue dispatch, client reinit and event-log replay. Bars overlay the two runs: is main, marks where this run lands, bridges the gap when this run has more samples in a bucket.

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, deployment clocks) · STSO: step-to-step overhead (gap between consecutive step bodies) · WO: workflow overhead (whole-run time outside step bodies, in-deployment anchored) · SL: stream latency (in-deployment write → read propagation, readAt - writtenAt) · SO: stream overhead (end-to-end write+consume time beyond the modelled generation window)

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 · stream latency: parallel reader/writer steps on a dedicated stream; SL is the in-deployment write->read propagation (readAt - writtenAt) · stream overhead (text): writer streams 300 variable-length text token deltas paced at 100/s for 3s (a haiku-size LLM's token throughput) while a parallel reader drains the whole stream; SO is the end-to-end write+consume time beyond the 3s generation window (overhead/backpressure) · stream overhead (structured): same workload as stream overhead (text), but each delta is an AI-SDK-style structured object ({ type: 'text-delta', id, text }) instead of a raw string, so the SO gap vs the text scenario is the added serialization cost

🔴 marks a percentile over its target (within target is left unmarked). Targets (p75/p90/p99, ms) — TTFS 200/300/600 · SL 50/60/125 · SO 250/500/1000

All metrics are measured from deployment-side timestamps only. Runs are triggered by an in-deployment route that stamps the anchor (clientStart) right before start(), so the CI runner’s request and its path through api.vercel.com sit outside every measured window. TTFS = in-deployment start() → first step body (turbo uses the in-process fast path, non-turbo the dispatch path), and includes the VQS dispatch hop plus any /flow cold start. STSO/WO are measured between step bodies on the deployment. SL is measured inside the workflow (parallel reader/writer steps), so it no longer includes the api.vercel.com read path.

Cold starts are kept in the numbers on purpose — they are part of real bursty-workload latency. The workbench deployment cold-starts the /flow invocation for a large fraction of runs, inflating P75+; the Best column shows the fastest (warm-start) sample for comparison.

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Sim World

Simulated world deterministic testing for races. Traces

🟠 Mint-ordered log — 3 fail of 41 total

log=mint-ordered · 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
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 failed 9 1.0m MISMATCH 1
in-flight-before-decision-counted failed 9 1.0m MISMATCH 1
in-flight-after-decision failed 9 1.0m MISMATCH 1
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-mint.txt

🟢 Append-only log — 0 fail of 41 total

log=append-only · 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
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-append-only.txt

Comment thread e2e-diagnostics-nextjs-turbopack-local.json Outdated
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Event Log Race Repro

12 of 14 latest repro runs hit event-log regressions.

Run History

Metric 2026-08-12 23:19 UTC #1
logs / deploy
2026-08-12 23:32 UTC #1
logs / deploy
2026-08-13 00:04 UTC #1
logs / deploy
2026-08-13 00:46 UTC #1
logs / deploy
2026-08-13 01:03 UTC #1
logs / deploy
2026-08-13 01:32 UTC #1
logs / deploy
2026-08-13 01:50 UTC #1
logs
2026-08-13 02:00 UTC #1
logs / deploy
Result 12/14 regressions 12/14 regressions 12/14 regressions 12/14 regressions 12/14 regressions 12/14 regressions missing result file 12/14 regressions
Total 14 14 14 14 14 14 0 14
completed 2 2 2 2 2 2 0 2
CORRUPTED_EVENT_LOG 11 10 0 11 0 0 0 0
USER_ERROR 0 0 0 0 0 0 0 0
RUNTIME_ERROR 0 0 0 0 0 0 0 0
stuck 1 2 12 1 12 12 0 12
other 0 0 0 0 0 0 0 0
infra 0 0 0 0 0 0 0 0
Config 14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8 14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8 14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8 14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8 14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8 14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8 14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timing watchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms watchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms watchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms watchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms watchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms watchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms watchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

Scenario Total completed CORRUPTED_EVENT_LOG USER_ERROR RUNTIME_ERROR stuck other infra
step-storm 6 0 0 0 0 6 0 0
hook-storm 6 0 0 0 0 6 0 0
hook-sleep 2 2 0 0 0 0 0 0

Latest Non-Completed Runs

Scenario Attempt Outcome Status Error code Run
step-storm 5 stuck running wrun_41KZWD2FZ40GQKAJH53V2EBQ6C
step-storm 6 stuck running wrun_41KZWD2FZ50GK2MZT5062HNJ80
step-storm 3 stuck running wrun_41KZWD2FZ40GQKAJH53V2EBQ6D
step-storm 1 stuck running wrun_41KZWD2FZ50GK2MZT5062HNJ7Z
step-storm 4 stuck running wrun_41KZWD2FZ50GK2MZT5062HNJ7X
step-storm 2 stuck running wrun_41KZWD2FZ50GK2MZT5062HNJ7Y
hook-storm 4 stuck running wrun_41KZWDA51Z0GGVVKNTGXJ50BVJ
hook-storm 3 stuck running wrun_41KZWDA51Z0GGVVKNTGXJ50BVH
hook-storm 1 stuck running wrun_41KZWDA51Y0GGJKRQB9FN3MCSA
hook-storm 5 stuck running wrun_41KZWDA51Z0GGVVKNTGXJ50BVK
hook-storm 2 stuck running wrun_41KZWDA51Y0GGJKRQB9FN3MCSB
hook-storm 6 stuck running wrun_41KZWDA51Z0GGVVKNTGXJ50BVM

… resilient dispatch by default

Three removals that stand on their own, split out of the v5 API switch.

**Pre-slot event ids.** `maxEventSlot`, `findEventSlotGap`, and the step
executor's slot observer went through a lenient decoder that answered "no
position" for an id that is not a slot. That leniency *was* the pre-slot
support, and it is the wrong shape now: a write whose `eventCount` is absent is
indistinguishable, to a World, from one that honestly loaded nothing. They call
`requireEventSlot` and throw. Skew protection is what makes it safe on Vercel —
a run executes on the deployment that created it, so a build carrying this
never replays a run created before slot ids.

**The `preconditionGuard` capability.** Every World is now assumed to be able
to refuse a stale replay-context write, so the three behaviors that keyed on
the flag apply unconditionally: the per-step inline event-log delta stays
enabled while the run has an open hook, an inline step's `step_started` claim
is awaited before the body runs, and resilient dispatch stops consulting it.
Before this only world-vercel declared it, so for world-local and
world-postgres this is a behavior change rather than a no-op — they now pay the
await-then-run claim while a hook is open, and get the inline delta in that
same case.

**`WORKFLOW_RESILIENT_STEP_DISPATCH`.** Off by default, `=1` to opt in. The
publish races the create's verdict: a World that refuses the `step_created`
sends the runtime back to replay while the payload-carrying message is already
out, and nothing orders the refusal before the consumer's redelivery re-ensure.
It was gated on the capability that no longer exists, and was already off for
world-vercel, which declared it.

`@workflow/world-sim` moves to slot ids with the runtime, since it drives the
real one. The book's mint-ordered count goes 35/6/6 to 38/3/3 and append-only
stays 41/0/0: four of the six reds staged a read missing an event the log
already held, which under slots is a gap the runtime re-reads past.

Also deletes `.changeset/windows-preload-timeout.md`, committed here by mistake.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
VaguelySerious and others added 5 commits August 12, 2026 18:48
Every `events` read and write goes to `/api/v5/...`. The wire frame is
unchanged, so the file keeps its name and its schemas: what the route selects
is server behavior, not a format. On v5 the backend no longer writes the Step
and Wait rows on the event path, so a v5 run reads back with no steps through
`steps.list` until the read model is rebuilt from events.

`runs.cancel` stays on `/v4/runs/cancel`: there is no v5 mirror of it.

`WORKFLOW_EVENTS_TRANSPORT=ws` reaches v5 too, decided on the backend side. Its
endpoint stays versioned on its own, which is about the frame envelope rather
than which write path a frame reaches.

`WORKFLOW_SERVER_URL_OVERRIDE` is pinned to the branch serving /v5 for the life
of this PR and must be reverted to '' before merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…now live

The retry ceiling is decided against a step's attempt number, and that number
came back from the World: the step row carried a counter its start patch
incremented. A World that stores only the log has no such counter, and the
attempt is on no event unless the writer puts it there — so every attempt
reported 1, no step ever exhausted its budget, and any failing step retried
forever. The writer already knows the number (it is what the pre-body ceiling
is enforced against), so it states it. Omitted when the caller supplied none,
so a World that counts for itself is not overwritten by a guess.

The e2e step assertions move off the storage listing. `--withData` was forcing
it, for two reasons that both changed: `attempt` is now on the analytics
listing, and a step's error is a payload no metadata listing ever carries —
that one reads the `step_failed` event, which holds it on every World.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t corrupted

Two invocations can run one step: a queue-dispatched step has no claim to lose,
its message can be delivered twice, and both deliveries start a step that
already exists. When the loser's body then fails behind the winner's result,
the `step_retrying` it records repeats no class, so no consumer wants it and
the replay was calling it divergence — failing a run that had finished.

Measured rather than argued: with the backend's inline claim fenced and this
reverted, `event-log-race-repro` corrupts 12 of 12 storm runs; with it, 0. It
changes one deliberate expectation — a start behind a completion that repeats
no class used to be divergence.

The harness now prints a failing attempt's event-log tail. It reported
`CORRUPTED_EVENT_LOG` and nothing else: the message the runtime failed on does
not survive into the run row, and the observability tables are not populated
for a preview backend, so which two writes collided had to be inferred from the
shape of the code. Inferring it is how two diagnoses went wrong, and the tail
is what showed the remaining failure is an infinite retry loop rather than the
corruption it was assumed to be.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`writeDiagnosticsSidecar` writes it into the repo root on every e2e run, and
the gitignore rule added alongside its sibling does not apply to a file already
tracked. Committed here by mistake, from running the suite locally.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Step row answered three questions at execution time that nothing
answers on a World that materializes no step rows (the v5 event path):

1. **The input.** v4's step_started PATCH returned the row, whose inputRef
   was stored at step_created; the executor hydrates the body from that
   response. A bare start against the no-row path synthesizes its response
   from the event just committed, which carries no payload — so every
   queue-dispatched step hydrated `undefined` and failed with devalue's
   "Invalid input" before user code ran, retried, and failed again on the
   next delivery, forever.

2. **The attempt.** The row's counter was the retry-ceiling authority.
   With it gone the only cheap signal was the queue delivery count, which
   resets to 1 on every fresh publish — and every retry after
   step_retrying IS a fresh publish, so the ceiling's fast gate never
   opened, the writer stamped attempt 1 on every start, and a failing
   step never exhausted its budget. This is what turned the race-repro
   storms into 12/12 stuck-at-running.

3. **The start fence.** The no-row write path reads a write's refusal out
   of the slots it skipped, which requires the writer to name its log
   position — and the queued executor had none to name, so a start behind
   the step's recorded outcome was told it won and re-ran the body.

The dispatcher — a replay that holds the loaded log — knows all three, so
the step dispatch message now carries them: `stepInput` (the same
serialized bytes step_created stored, when binary, within the resilient
size bound, and on a CBOR transport), and a new `stepContext` with the
1-based attempt this dispatch asks for plus the dispatcher's slot
snapshot. The background executor takes max(delivery count, declared
attempt) as its ceiling input (still verified against the recorded start
count before failing the step), seeds its writes with the snapshot, and
hydrates from the response, then the message, then a step_created
read-back — and refuses to run a body with no input at all rather than
handing undefined to the deserializer. Every field is optional and
advisory: old messages and old consumers behave exactly as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

event-log-race-repro Run the event log race reproduction job

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant