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

@VaguelySeriousVaguelySerious 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-botBot 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
NameType
@workflow/worldPatch
workflowPatch
@workflow/corePatch
@workflow/world-vercelPatch
@workflow/cliPatch
@workflow/vitestPatch
@workflow/web-sharedPatch
@workflow/webPatch
@workflow/world-localPatch
@workflow/world-postgresPatch
@workflow/world-testingPatch
@workflow/buildersPatch
@workflow/nextPatch
@workflow/nitroPatch
@workflow/astroPatch
@workflow/nestPatch
@workflow/rollupPatch
@workflow/sveltekitPatch
@workflow/vitePatch
@workflow/nuxtPatch

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

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

@vercel

vercelBot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

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

ProjectDeploymentActionsUpdated (UTC)
example-nextjs-workflow-turbopackReadyReadyPreviewAug 13, 2026 2:23am
example-nextjs-workflow-webpackReadyReadyPreviewAug 13, 2026 2:23am
example-workflowReadyReadyPreviewAug 13, 2026 2:23am
workbench-astro-workflowReadyReadyPreviewAug 13, 2026 2:23am
workbench-express-workflowReadyReadyPreviewAug 13, 2026 2:23am
workbench-fastify-workflowReadyReadyPreviewAug 13, 2026 2:23am
workbench-hono-workflowReadyReadyPreviewAug 13, 2026 2:23am
workbench-nestjs-workflowReadyReadyPreviewAug 13, 2026 2:23am
workbench-nitro-workflowReadyReadyPreviewAug 13, 2026 2:23am
workbench-nuxt-workflowReadyReadyPreviewAug 13, 2026 2:23am
workbench-python-workflowErrorErrorAug 13, 2026 2:23am
workbench-sveltekit-workflowReadyReadyPreviewAug 13, 2026 2:23am
workbench-tanstack-start-workflowReadyReadyPreviewAug 13, 2026 2:23am
workbench-vite-workflowReadyReadyPreviewAug 13, 2026 2:23am
workflow-docsReadyReadyPreview, v0Aug 13, 2026 2:23am
workflow-swc-playgroundReadyReadyPreviewAug 13, 2026 2:23am
workflow-tarballsReadyReadyPreviewAug 13, 2026 2:23am
workflow-webReadyReadyPreviewAug 13, 2026 2:23am

@github-actions

github-actionsBot 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
PassedFailedSkippedTotal
❌ ▲ Vercel Production31521615873900
✅ 💻 Local Development381005584368
✅ 📦 Local Production381005584368
✅ 🐘 Local Postgres381005584368
✅ 🪟 Windows31200312
✅ vercel-multi-region270027
Total14921161226117343
Details by Category

❌ ▲ Vercel Production

AppPassedFailedSkipped
❌ astro-node121728
❌ astro-quickjs122628
❌ example-node121728
❌ example-quickjs122628
❌ express-node121728
❌ express-quickjs122628
❌ fastify-node122628
❌ fastify-quickjs122628
❌ hono-node121728
❌ hono-quickjs122628
❌ nest-node122628
❌ nest-quickjs122628
❌ nextjs-turbopack-node14673
❌ nextjs-webpack-node14673
❌ nextjs-webpack-quickjs14763
❌ nitro-node121728
❌ nitro-quickjs122628
❌ nuxt-node121728
❌ nuxt-quickjs122628
❌ sveltekit-node14169
❌ sveltekit-quickjs14169
❌ tanstack-start-node120828
❌ tanstack-start-quickjs122628
❌ vite-node121728
❌ vite-quickjs122628

✅ 💻 Local Development

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 📦 Local Production

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🐘 Local Postgres

AppPassedFailedSkipped
✅ astro-stable-node130026
✅ astro-stable-quickjs130026
✅ express-stable-node130026
✅ express-stable-quickjs130026
✅ fastify-stable-node130026
✅ fastify-stable-quickjs130026
✅ hono-stable-node130026
✅ hono-stable-quickjs130026
✅ nest-stable-node130026
✅ nest-stable-quickjs130026
✅ nextjs-turbopack-canary-node137019
✅ nextjs-turbopack-canary-quickjs137019
✅ nextjs-turbopack-stable-node15600
✅ nextjs-turbopack-stable-quickjs15600
✅ nextjs-webpack-canary-node137019
✅ nextjs-webpack-canary-quickjs137019
✅ nextjs-webpack-stable-node15600
✅ nextjs-webpack-stable-quickjs15600
✅ nitro-stable-node130026
✅ nitro-stable-quickjs130026
✅ nuxt-stable-node130026
✅ nuxt-stable-quickjs130026
✅ sveltekit-stable-node14907
✅ sveltekit-stable-quickjs14907
✅ tanstack-start-node130026
✅ tanstack-start-quickjs130026
✅ vite-stable-node130026
✅ vite-stable-quickjs130026

✅ 🪟 Windows

AppPassedFailedSkipped
✅ nextjs-turbopack-node15600
✅ nextjs-turbopack-quickjs15600

✅ vercel-multi-region

AppPassedFailedSkipped
✅ nextjs-turbopack2700

📋 View full workflow run

@github-actions

github-actionsBot 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

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep230 (-4.6%)334 🔴 (-71%) 💚575 🔴 (-51%) 💚1339 🔴 (+5.9%)30
TTFSstream221 (-19%) 💚344 🔴 (-70%) 💚412 🔴 (-66%) 💚1385 🔴 (-12%)30
TTFShook + stream358 (-11%)534 🔴 (-62%) 💚616 🔴 (-57%) 💚1007 🔴 (-32%) 💚30
STSO1020 steps (inline)110 (-2.7%)184 (-13%)214 (-15%) 💚336 (-19%) 💚1019
WO1020 steps179536 (-14%)179536 (-14%)179536 (-14%)179536 (-14%)1
SLstream latency108 (+11%)167 🔴 (+3.7%)277 🔴 (+28%) 🔻838 🔴 (+124%) 🔻30
SOstream overhead (text)149 (-0.7%)245 (-14%)273 (-55%) 💚616 (-20%) 💚30
SOstream 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

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1327 (+451%) 🔻1390 🔴 (+22%) 🔻1404 🔴 (+20%) 🔻1663 🔴 (+32%) 🔻30
TTFSstream1312 (+379%) 🔻1365 🔴 (+17%) 🔻1376 🔴 (+14%)1469 🔴 (-6.3%)30
TTFShook + stream1534 (+284%) 🔻1668 🔴 (+20%) 🔻1694 🔴 (+19%) 🔻1730 🔴 (+16%) 🔻30
STSO1020 steps (inline)98 (-13%)133 (-37%) 💚151 (-40%) 💚238 (-43%) 💚1019
WO1020 steps133512 (-36%) 💚133512 (-36%) 💚133512 (-36%) 💚133512 (-36%) 💚1
SLstream latency78 (-20%) 💚109 🔴 (-32%) 💚114 🔴 (-47%) 💚309 🔴 (-17%) 💚30
SOstream overhead (text)97 (-35%) 💚149 (-48%) 💚164 (-73%) 💚245 (-68%) 💚30
SOstream 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

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep314 (+30%) 🔻1330 🔴 (+16%) 🔻1374 🔴 (+18%) 🔻1431 🔴 (+13%)30
TTFSstream1273 (+365%) 🔻1318 🔴 (+13%)1328 🔴 (+9.7%)1370 🔴 (-13%)30
TTFShook + stream1530 (+283%) 🔻1633 🔴 (+17%) 🔻1679 🔴 (+18%) 🔻2014 🔴 (+35%) 🔻30
STSO1020 steps (inline)99 (-12%)141 (-33%) 💚167 (-34%) 💚283 (-32%) 💚1019
WO1020 steps142959 (-32%) 💚142959 (-32%) 💚142959 (-32%) 💚142959 (-32%) 💚1
SLstream latency79 (-19%) 💚103 🔴 (-36%) 💚113 🔴 (-48%) 💚479 🔴 (+28%) 🔻30
SOstream overhead (text)103 (-31%) 💚146 (-49%) 💚166 (-73%) 💚203 (-74%) 💚30
SOstream 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

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1342 (+457%) 🔻1466 🔴 (+28%) 🔻1517 🔴 (+30%) 🔻1592 🔴 (+26%) 🔻30
TTFSstream450 (+64%) 🔻1398 🔴 (+20%) 🔻1424 🔴 (+18%) 🔻1750 🔴 (+12%)30
TTFShook + stream1591 (+298%) 🔻1758 🔴 (+26%) 🔻1829 🔴 (+29%) 🔻1911 🔴 (+28%) 🔻30
STSO1020 steps (inline)105 (-7.1%)159 (-25%) 💚176 (-30%) 💚276 (-33%) 💚1019
WO1020 steps159471 (-24%) 💚159471 (-24%) 💚159471 (-24%) 💚159471 (-24%) 💚1
SLstream latency114 (+18%) 🔻169 🔴 (+5.0%)234 🔴 (+8.3%)341 🔴 (-8.8%)30
SOstream overhead (text)126 (-16%) 💚178 (-37%) 💚188 (-69%) 💚414 (-47%) 💚30
SOstream 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

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep1180 (+390%) 🔻1350 🔴 (+18%) 🔻1378 🔴 (+18%) 🔻1555 🔴 (+23%) 🔻30
TTFSstream185 (-32%) 💚1227 🔴 (+5.3%)1245 🔴 (+2.8%)1344 🔴 (-14%)30
TTFShook + stream1428 (+257%) 🔻1611 🔴 (+16%) 🔻1653 🔴 (+16%) 🔻1863 🔴 (+25%) 🔻30
STSO1020 steps (inline)105 (-7.1%)142 (-33%) 💚158 (-37%) 💚268 (-35%) 💚1019
WO1020 steps142843 (-32%) 💚142843 (-32%) 💚142843 (-32%) 💚142843 (-32%) 💚1
SLstream latency86 (-11%)116 🔴 (-28%) 💚132 🔴 (-39%) 💚152 🔴 (-59%) 💚30
SOstream overhead (text)100 (-33%) 💚148 (-48%) 💚159 (-74%) 💚288 (-63%) 💚30
SOstream 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

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep311 (+29%) 🔻1362 🔴 (+19%) 🔻1424 🔴 (+22%) 🔻1503 🔴 (+19%) 🔻30
TTFSstream1284 (+369%) 🔻1344 🔴 (+15%) 🔻1384 🔴 (+14%)1447 🔴 (-7.7%)30
TTFShook + stream1522 (+281%) 🔻1707 🔴 (+23%) 🔻1746 🔴 (+23%) 🔻1797 🔴 (+21%) 🔻30
STSO1020 steps (inline)108 (-4.4%)139 (-34%) 💚155 (-38%) 💚263 (-37%) 💚1019
WO1020 steps141239 (-32%) 💚141239 (-32%) 💚141239 (-32%) 💚141239 (-32%) 💚1
SLstream latency89 (-8.2%)117 🔴 (-27%) 💚159 🔴 (-26%) 💚264 🔴 (-29%) 💚30
SOstream overhead (text)110 (-27%) 💚172 (-39%) 💚220 (-64%) 💚1722 🔴 (+122%) 🔻30
SOstream 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

MetricScenarioBest (ms)P75 (ms)P90 (ms)P99 (ms)Samples
TTFSstep421 (+75%) 🔻1377 🔴 (+21%) 🔻1392 🔴 (+19%) 🔻1616 🔴 (+28%) 🔻30
TTFSstream1303 (+376%) 🔻1364 🔴 (+17%) 🔻1387 🔴 (+15%)1413 🔴 (-9.9%)30
TTFShook + stream1593 (+298%) 🔻1674 🔴 (+20%) 🔻1699 🔴 (+20%) 🔻1780 🔴 (+20%) 🔻30
STSO1020 steps (inline)105 (-7.1%)146 (-31%) 💚166 (-34%) 💚245 (-41%) 💚1019
WO1020 steps145228 (-30%) 💚145228 (-30%) 💚145228 (-30%) 💚145228 (-30%) 💚1
SLstream latency97 (±0%)118 🔴 (-27%) 💚128 🔴 (-41%) 💚184 🔴 (-51%) 💚30
SOstream overhead (text)116 (-23%) 💚164 (-42%) 💚186 (-70%) 💚733 (-5.3%)30
SOstream 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-actionsBot 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

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisionfailed91.0mMISMATCH1
in-flight-before-decision-countedfailed91.0mMISMATCH1
in-flight-after-decisionfailed91.0mMISMATCH1
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-mint.txt

🟢 Append-only log — 0 fail of 41 total

log=append-only · fence=per-spec

scenariooutcomeeventsvirtreplayviolations
smoke-no-stepscompleted30msok0
smoke-one-stepcompleted60msok0
hook-at-step-startedcompleted120msok0
hook-at-step-completedcompleted120msok0
hook-at-hook-createdcompleted120msok0
deadline-hook-winscompleted71.0hok0
deadline-expirescompleted71.0hok0
long-sleepcompleted1130.0dok0
hook-never-arrivesstalled30msskipped0
step-retries-twicecompleted102.0sok0
parallel-stepscompleted90msok0
hook-on-execution-statecompleted120msok0
peek-hook-before-branchcompleted120msok0
peek-hook-after-branchcompleted120msok0
peek-hook-at-registrationcompleted120msok0
race-hook-before-probecompleted120msok0
race-hook-after-probecompleted120msok0
race-duplicate-deliverycompleted130msok0
attr-hook-before-stepcompleted110msok0
attr-hook-after-stepcompleted110msok0
attr-from-step-bodycompleted130msok0
fork-hook-after-timeoutcompleted141.0mok0
fork-hook-before-timeoutcompleted141.0mok0
count-hook-after-timeoutcompleted171.0mok0
count-hook-before-timeoutcompleted201.0mok0
stale-read-step-count-forkcompleted201.0mok0
stale-read-equal-step-countscompleted141.0mok0
step-vs-step-forkcompleted120msok0
step-vs-step-fork-fencedcompleted120msok0
fence-catches-benign-directioncompleted125msok0
in-flight-before-decisioncompleted171.0mok0
in-flight-before-decision-countedcompleted171.0mok0
in-flight-after-decisioncompleted192.0mok0
stale-read-step-count-fork-fencedcompleted201.0mok0
fork-hook-winscompleted131.0mok0
fork-timeout-winscompleted131.0mok0
unclaimed-payload-under-forkcompleted171.0mok0
claimed-payload-under-forkcompleted171.0mok0
writers-independent-step-bodiescompleted120msok0
writers-scripted-tempocompleted120msok0
cancel-mid-stepcancelled70msskipped0

Full trace: world-sim-append-only.txt

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

github-actionsBot 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

Metric2026-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
Result12/14 regressions12/14 regressions12/14 regressions12/14 regressions12/14 regressions12/14 regressionsmissing result file12/14 regressions
Total141414141414014
completed22222202
CORRUPTED_EVENT_LOG11100110000
USER_ERROR00000000
RUNTIME_ERROR00000000
stuck121211212012
other00000000
infra00000000
Config14 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x814 runs / step-storm 6, hook-storm 6, hook-sleep 2 / c8 / 6x8
Timingwatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000mswatchdog 2500ms / step 2200±250ms / stagger 400ms / poke 750ms / timeout 240000ms

Latest Scenario Breakdown

ScenarioTotalcompletedCORRUPTED_EVENT_LOGUSER_ERRORRUNTIME_ERRORstuckotherinfra
step-storm60000600
hook-storm60000600
hook-sleep22000000

Latest Non-Completed Runs

ScenarioAttemptOutcomeStatusError codeRun
step-storm5stuckrunningwrun_41KZWD2FZ40GQKAJH53V2EBQ6C
step-storm6stuckrunningwrun_41KZWD2FZ50GK2MZT5062HNJ80
step-storm3stuckrunningwrun_41KZWD2FZ40GQKAJH53V2EBQ6D
step-storm1stuckrunningwrun_41KZWD2FZ50GK2MZT5062HNJ7Z
step-storm4stuckrunningwrun_41KZWD2FZ50GK2MZT5062HNJ7X
step-storm2stuckrunningwrun_41KZWD2FZ50GK2MZT5062HNJ7Y
hook-storm4stuckrunningwrun_41KZWDA51Z0GGVVKNTGXJ50BVJ
hook-storm3stuckrunningwrun_41KZWDA51Z0GGVVKNTGXJ50BVH
hook-storm1stuckrunningwrun_41KZWDA51Y0GGJKRQB9FN3MCSA
hook-storm5stuckrunningwrun_41KZWDA51Z0GGVVKNTGXJ50BVK
hook-storm2stuckrunningwrun_41KZWDA51Y0GGJKRQB9FN3MCSB
hook-storm6stuckrunningwrun_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>
VaguelySeriousand 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

event-log-race-reproRun the event log race reproduction job

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@VaguelySerious