Skip to content

[e2e] Port 14 more fixtures to the Python workbench app - #3495

Draft
fantix wants to merge 8 commits into
fantix/python-conformancefrom
fantix/python-conformance-more
Draft

[e2e] Port 14 more fixtures to the Python workbench app#3495
fantix wants to merge 8 commits into
fantix/python-conformancefrom
fantix/python-conformance-more

Conversation

@fantix

@fantix fantix commented Aug 12, 2026

Copy link
Copy Markdown
Member

Stacked on #3369 — review that one first; this PR's diff is against its branch.

Local lane: 8 passed | 129 skipped26 passed | 111 skipped of 137.

Commits

  1. Repin vercel-py to 5c77acb — two commits past the old pin: Fix workflow stack traces (no more evalmachine) #256/Bug: ERR_REQUIRE_ESM when using @workflow/world-postgres in Next.js App Router #258, the write and read halves of the streaming API. get_writable() did not exist before them.
  2. Port 14 fixtures — races (5), streams (3), retries (4), child runs, metadata.
  3. Port the hook/sleep cluster (4) — and retire the "hooks need a design decision" framing: HookEvent implements __aiter__/__anext__, so for await (const p of hook) ports to async for payload in hook unchanged.
  4. Exempt hookWithSleepFinalStepWorkflow — Vercel lane only, encp. See below.
  5. Repin to the rebased resilient-start stack — takes the two race fixtures off the exemption list.
  6. Correct a stale README claim about encp.

The pin is not on vercel-py main

It is pin/resilient-start-on-streaming-main (4b70f10), a throwaway branch: main at 5c77acb with vercel-py #282/#283/#284 (resilient start) cherry-picked on top. The stack was written against 02173de, before the streaming API, so pinning its own head would take get_writable() away and break three stream fixtures. One conflict, in _events_create_impl's id minting, resolved toward main's self._new_id(...). pyproject.toml carries the reasoning next to the rev. Repin to main when the stack merges.

It buys two exemptions: sleepWinsRaceWorkflow and stepWinsRaceWorkflow went from failing ~half of all runs to passing, because the run's first delivery no longer 500s on a run row that has not landed, and so no longer costs the ~5s the world waits before redelivering — the entire budget those two tests measure.

Six unsupported entries, five upstream causes

entry cause
resilient start A defect in the stack that fixes it: RunInput.input is typed Any, so the Uint8Array envelope the local world's JSON queue wraps bytes in reaches NonFinalWorkflowRun.input, which wants bytes | str. Local world only — Vercel's queue is CBOR and the stack's unit tests use fakes with no transport.
hookWithSleepFinalStepWorkflow encp. On Vercel the driver resumes a hook as an external client with no symmetric key, so the payload is sealed to the run's public key; vercel-py reads encr and not encp, and the run fails on its first payload. Exempting it costs a local passunsupported is not lane-aware — but the alternative was leaving the Vercel lane red on a gap this repo cannot close.
hookWithSleepWorkflow Stalls resuming from hook_received straight into a new step. Both neighbours pass, which is what makes it specific.
hookTokenReuseLoopWorkflow hook_created / hook_disposed flushed with no ordering — the #2777 shape.
FatalError A thrown error loses its identity across the event log.

Three of those five were found by running this suite, which is the argument for the lane.

What is left

Everything still skipped names a missing API rather than porting effort: hooks (15 — metadata on BaseHook.wait(), where the wire model already carries the field, and getConflict()), setAttributes (9), abort (3), and single names like RetryableError and getWorkflowMetadata.

Testing

26 passed | 111 skipped (137) on world-local, two consecutive full-suite runs. No changeset — .changeset/python-workbench-app.md on the base branch already covers this app.

🤖 Generated with Claude Code

fantix and others added 2 commits August 12, 2026 14:08
`5c77acb` is two commits past the old pin: vercel-py #256 and #258, the
write and read halves of the workflow streaming API. That is what makes
`get_writable()` exist at all, so it is the prerequisite for the stream
fixtures in the next commit rather than a routine bump.

Relocked the documented way — `rm uv.lock && uv lock --no-config`, twice,
then `grep -c 'source = { git' uv.lock` to confirm all 8 sibling packages
still resolve from the checkout rather than silently falling back to PyPI.
The lock moves by exactly the rev, as it should.

Local suite unchanged at `8 passed | 129 skipped` on this commit alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Takes the local lane from `8 passed | 129 skipped` to `23 passed | 114
skipped` of 137. Every fixture here was picked because the Python SDK
grew the API it needs, not because it looked easy:

- **Races** (`promiseRace`, `promiseAny`, `sleepWinsRace`, `stepWinsRace`,
  `promiseRaceStressTest`). `Promise.race` / `Promise.any` have no asyncio
  spelling that takes bare awaitables, so two shared helpers supply them.
  Both resolve ties by argument order rather than by `asyncio.wait`'s set
  iteration order, which would be a latent replay divergence the first time
  two steps landed in the same turn.
- **Streams** (`outputStream`, `outputStreamInsideStep`, `utf8Stream`) —
  unlocked by the pin in the previous commit. Both spellings are covered
  because they take different paths through the SDK: a handle the workflow
  body creates and passes into a step's arguments, and `get_writable()`
  called inside the step itself.
- **Retries** (`errorRetrySuccess`, `errorRetryDisabled`, `errorRetryFatal`,
  `errorFatalCatchable`) via `get_step_metadata().attempt`, `max_retries=0`
  and `FatalError`.
- **Child runs** (`spawnWorkflowFromStep`) and **`metadataFromHelper`**.

Four new `unsupported` entries, and they are two defects, not four:

- `sleepWinsRaceWorkflow` / `stepWinsRaceWorkflow` pick the right winner
  every time. They are the only two tests in the suite that bound a run's
  elapsed time from *above*, which makes them the only two that notice the
  ~5s `world-local` waits before redelivering a first delivery the app 500'd
  because it could not read the run row yet. Same upstream gap as the
  `resilient start` entry already there; all three come out together.
- The two `FatalError` tests fail on the half Python cannot do. The step
  lifecycle is right — one attempt, run fails on it — but a thrown error
  loses its identity across the event log, so the body catches a
  `RuntimeError` and the run's `errorCode` is `RuntimeError` rather than
  `USER_ERROR`.

What is left is now blocked on named APIs rather than on porting effort:
hooks (19 tests, a shape difference), `setAttributes` (9, no equivalent),
distributed abort, `getWorkflowMetadata`, `RetryableError`, and the two
fixtures that call `start()` from a workflow body, which the Python sandbox
denies by design.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@fantix
fantix requested a review from a team as a code owner August 12, 2026 18:09
@changeset-bot

changeset-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 3f7188d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

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

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a 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 14, 2026 1:19am
example-nextjs-workflow-webpack Ready Ready Preview Aug 14, 2026 1:19am
example-workflow Ready Ready Preview Aug 14, 2026 1:19am
workbench-astro-workflow Ready Ready Preview Aug 14, 2026 1:19am
workbench-express-workflow Ready Ready Preview Aug 14, 2026 1:19am
workbench-fastify-workflow Ready Ready Preview Aug 14, 2026 1:19am
workbench-hono-workflow Ready Ready Preview Aug 14, 2026 1:19am
workbench-nestjs-workflow Ready Ready Preview Aug 14, 2026 1:19am
workbench-nitro-workflow Ready Ready Preview Aug 14, 2026 1:19am
workbench-nuxt-workflow Ready Ready Preview Aug 14, 2026 1:19am
workbench-python-workflow Ready Ready Preview Aug 14, 2026 1:19am
workbench-sveltekit-workflow Ready Ready Preview Aug 14, 2026 1:19am
workbench-tanstack-start-workflow Ready Ready Preview Aug 14, 2026 1:19am
workbench-vite-workflow Ready Ready Preview Aug 14, 2026 1:19am
workflow-swc-playground Ready Ready Preview Aug 14, 2026 1:19am
workflow-tarballs Ready Ready Preview Aug 14, 2026 1:19am
workflow-web Ready Ready Preview Aug 14, 2026 1:19am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
workflow-docs Skipped Skipped Aug 14, 2026 1:19am

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

Some tests failed

❌ Failed E2E Tests

🐘 Local Postgres (1 failed)

express-stable-quickjs (1 failed):

  • hookWorkflow is not resumable via public webhook endpoint | wrun_41KZYXKNDQ0GHN43N3C8J9PVZR

E2E Test Summary

Summary
Passed Failed Skipped Total
✅ ▲ Vercel Production 3492 0 720 4212
✅ 💻 Local Development 3673 0 539 4212
✅ 📦 Local Production 3810 0 558 4368
❌ 🐘 Local Postgres 3809 1 558 4368
✅ 🪟 Windows 312 0 0 312
✅ 🌐 Cross-language Conformance 27 0 110 137
✅ vercel-multi-region 27 0 0 27
Total 15150 1 2485 17636
Details by Category

✅ ▲ Vercel Production

App Passed Failed Skipped
✅ astro-node 128 0 28
✅ astro-quickjs 128 0 28
✅ example-node 128 0 28
✅ example-quickjs 128 0 28
✅ express-node 128 0 28
✅ express-quickjs 128 0 28
✅ fastify-node 128 0 28
✅ fastify-quickjs 128 0 28
✅ hono-node 128 0 28
✅ hono-quickjs 128 0 28
✅ nest-node 128 0 28
✅ nest-quickjs 128 0 28
✅ nextjs-turbopack-node 153 0 3
✅ nextjs-turbopack-quickjs 153 0 3
✅ nextjs-webpack-node 153 0 3
✅ nextjs-webpack-quickjs 153 0 3
✅ nitro-node 128 0 28
✅ nitro-quickjs 128 0 28
✅ nuxt-node 128 0 28
✅ nuxt-quickjs 128 0 28
✅ python-node 26 0 130
✅ sveltekit-node 147 0 9
✅ sveltekit-quickjs 147 0 9
✅ tanstack-start-node 128 0 28
✅ tanstack-start-quickjs 128 0 28
✅ vite-node 128 0 28
✅ vite-quickjs 128 0 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-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 129 1 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

✅ 🌐 Cross-language Conformance

App Passed Failed Skipped
✅ python 27 0 110

✅ 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

Sim World

Simulated world deterministic testing for races. Traces

🟠 Mint-ordered log — 6 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 17 1.0m MISMATCH 1
stale-read-equal-step-counts completed 14 1.0m MISMATCH 1
step-vs-step-fork completed 12 0ms MISMATCH 1
step-vs-step-fork-fenced completed 12 0ms MISMATCH 1
fence-catches-benign-direction completed 12 5ms ok 0
in-flight-before-decision completed 17 1.0m MISMATCH 1
in-flight-before-decision-counted completed 20 1.0m ok 0
in-flight-after-decision failed 14 2.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

`23 passed | 114 skipped` → `25 passed | 112 skipped`, and the more useful
number is the other one: the "hooks need a design decision upstream" bucket
was 19 tests and is 15, because the premise was wrong. `HookEvent`
implements `__await__` *and* `__aiter__`/`__anext__`, so
`for await (const p of hook)` ports to `async for payload in hook`
unchanged. The real constraint is narrower — `Hook.set_result` calls
`hook_cls(**raw)`, so the payload's structural TypeScript type becomes a
declared dataclass, loose in exactly the fields the fixture leaves optional.

Two of the four pass and two are exempted, and both exemptions are defects
this suite found rather than ones predicted before it ran:

- `hookWithSleepWorkflow` stalls on its first payload. `hook_created`,
  `wait_created`, `hook_received` land; the step the body calls next never
  does, with no error and a 200 on the delivery. Both neighbours rule out
  the obvious causes — `hookWithSleepFinalStepWorkflow` takes the same hook
  twice before its one step and passes, `sleepWithSequentialStepsWorkflow`
  puts the same never-completing `sleep('1d')` in front of three sequential
  steps and passes — which leaves the transition only this fixture makes:
  resuming from `hook_received` straight into a new step suspension.
- `hookTokenReuseLoopWorkflow` never frees the token. vercel-py flushes
  `hook_created` and `hook_disposed` from one task group with no ordering,
  which is what upstream #2777 fixed on the TypeScript side.

`sleepWithSequentialStepsWorkflow` has no hook in it and was only ever
bucketed with them because it is their control. It is ported next to them
for the same reason: it is what makes the two failures above specific.

One translation trap is worth the comment it got in the fixture file:
`using hook` is not `try/finally`. A Python workflow body unwinds through a
`_SuspendException` on every suspension, so a `finally` around an `await`
runs once per turn instead of once at scope exit — disposing a hook there
deletes the suspension before the orchestrator can flush its `hook_created`,
and the run stalls with no hook for the driver to resume. Cost one debugging
round; should cost the next reader none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel
vercel Bot temporarily deployed to Preview – workflow-docs August 12, 2026 18:57 Inactive
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

commit 3f7188d · Fri, 14 Aug 2026 01:35:01 GMT · run logs

Backend: vercel · app: nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 363 (-4.7%) 1411 🔴 (+26%) 🔻 1433 🔴 (+26%) 🔻 1515 🔴 (-1.1%) 30
TTFS stream 1315 (+31%) 🔻 1411 🔴 (+33%) 🔻 1420 🔴 (+33%) 🔻 1489 🔴 (+35%) 🔻 30
TTFS hook + stream 518 (-59%) 💚 1750 🔴 (+26%) 🔻 1786 🔴 (+25%) 🔻 1956 🔴 (+21%) 🔻 30
STSO 1020 steps (inline) 126 (+2.4%) 173 (-9.4%) 198 (-14%) 343 (-41%) 💚 1019
WO 1020 steps 172756 (-12%) 172756 (-12%) 172756 (-12%) 172756 (-12%) 1
SL stream latency 108 (+37%) 🔻 157 🔴 (+43%) 🔻 216 🔴 (+67%) 🔻 507 🔴 (+48%) 🔻 30
SO stream overhead (text) 127 (+14%) 279 🔴 (+55%) 🔻 407 (+98%) 🔻 3439 🔴 (+469%) 🔻 30
SO stream overhead (structured) 125 (+30%) 🔻 217 (+39%) 🔻 232 (+39%) 🔻 3516 🔴 (+1832%) 🔻 30
📈 STSO distribution vs main (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: main 194368ms → this run 171365ms (Δ -23003ms, -12%)

  100-150 ms  ██████░░┃                 main 180  this 244   +64
  150-200 ms  ██████████████████████░┃  main 627  this 677   +50
  200-250 ms  █┃███                     main 134  this  69   -65
  250-300 ms  ┃                         main  29  this  10   -19
  300-350 ms  ┃                         main  15  this  10    -5
  350-400 ms  ┃                         main  11  this   6    -5
  400-450 ms  ┃                         main   4  this   1    -3
  450-500 ms  ┃                         main   5  this   1    -4
  500-550 ms  ┃                         main   3  this   0    -3
  550-600 ms  ┃                         main   1  this   1    +0
  600-650 ms  ┃                         main   5  this   0    -5
  650-700 ms  ┃                         main   1  this   0    -1
  750-800 ms  ┃                         main   1  this   0    -1
  800-850 ms  ┃                         main   1  this   0    -1
1100-1150 ms  ┃                         main   1  this   0    -1
4450-4500 ms  ┃                         main   1  this   0    -1
📜 Previous results (3)

1310bd0

Thu, 13 Aug 2026 15:32:20 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1405 (+536%) 🔻 1499 🔴 (+42%) 🔻 1501 🔴 (+40%) 🔻 1678 🔴 (+48%) 🔻 30
TTFS stream 1303 (+529%) 🔻 1438 🔴 (+34%) 🔻 1455 🔴 (+30%) 🔻 1550 🔴 (+20%) 🔻 30
TTFS hook + stream 629 (+61%) 🔻 1731 🔴 (+29%) 🔻 1812 🔴 (+31%) 🔻 2036 🔴 (+42%) 🔻 30
STSO 1020 steps (inline) 120 (-17%) 💚 168 (-20%) 💚 197 (-18%) 💚 302 (-33%) 💚 1019
WO 1020 steps 168901 (-18%) 💚 168901 (-18%) 💚 168901 (-18%) 💚 168901 (-18%) 💚 1
SL stream latency 89 (-9.2%) 130 🔴 (-16%) 💚 134 🔴 (-27%) 💚 180 🔴 (-39%) 💚 30
SO stream overhead (text) 119 (-7.0%) 167 (-30%) 💚 203 (-35%) 💚 2562 🔴 (+507%) 🔻 30
SO stream overhead (structured) 105 (-23%) 💚 145 (-30%) 💚 166 (-34%) 💚 219 (-75%) 💚 30

bb847d2

Wed, 12 Aug 2026 20:53:06 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 301 (-69%) 💚 1451 🔴 (+30%) 🔻 1470 🔴 (+22%) 🔻 1898 🔴 (+18%) 🔻 30
TTFS stream 218 (-79%) 💚 1441 🔴 (+28%) 🔻 1454 🔴 (+26%) 🔻 1594 🔴 (+32%) 🔻 30
TTFS hook + stream 455 (-4.6%) 1728 🔴 (+20%) 🔻 1747 🔴 (+17%) 🔻 1882 🔴 (+14%) 30
STSO 1020 steps (inline) 124 (+18%) 🔻 201 (+19%) 🔻 235 (+18%) 🔻 422 (+16%) 🔻 1019
WO 1020 steps 199461 (+20%) 🔻 199461 (+20%) 🔻 199461 (+20%) 🔻 199461 (+20%) 🔻 1
SL stream latency 94 (+6.8%) 152 🔴 (+17%) 🔻 186 🔴 (-7.5%) 286 🔴 (-7.7%) 30
SO stream overhead (text) 127 (+3.3%) 272 🔴 (+28%) 🔻 776 🔴 (+141%) 🔻 1300 🔴 (-2.5%) 30
SO stream overhead (structured) 128 (+32%) 🔻 300 🔴 (+38%) 🔻 403 (+60%) 🔻 822 (-95%) 💚 30

8bde3f6

Wed, 12 Aug 2026 19:14:18 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 270 (-70%) 💚 1388 🔴 (+18%) 🔻 1421 🔴 (-6.0%) 1459 🔴 (-15%) 30
TTFS stream 1302 (+30%) 🔻 1365 🔴 (+31%) 🔻 1396 🔴 (+32%) 🔻 1471 🔴 (+26%) 🔻 30
TTFS hook + stream 1267 (-3.2%) 1712 🔴 (+20%) 🔻 1775 🔴 (+17%) 🔻 2042 🔴 (-56%) 💚 30
STSO 1020 steps (inline) 117 (-3.3%) 165 (-9.8%) 195 (-10%) 333 (-11%) 1019
WO 1020 steps 165307 (-8.4%) 165307 (-8.4%) 165307 (-8.4%) 165307 (-8.4%) 1
SL stream latency 81 (-6.9%) 116 🔴 (-25%) 💚 130 🔴 (-54%) 💚 213 🔴 (-71%) 💚 30
SO stream overhead (text) 114 (-4.2%) 157 (-28%) 💚 205 (-22%) 💚 254 (-52%) 💚 30
SO stream overhead (structured) 107 (+2.9%) 162 (-12%) 207 (-2.8%) 594 (-1.0%) 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.

`24 passed | 113 skipped` → `26 passed | 111 skipped`, and two
`unsupported` entries come out: `sleepWinsRaceWorkflow` and
`stepWinsRaceWorkflow` pass on this rev.

The pin is a throwaway branch on vercel-py,
`pin/resilient-start-on-streaming-main` (`4b70f10`) — `main` at 5c77acb
with #282/#283/#284 cherry-picked on top. The rebase is the point: the
stack was written against 02173de, before the streaming API, so pinning
its own head would take `get_writable()` away and break three stream
fixtures. One cherry-pick conflict, in `_events_create_impl`'s id
minting, resolved toward main's `self._new_id(...)` helper. Repin to
plain `main` when the stack merges; `pyproject.toml` carries the whole
story next to the `rev`.

What it fixes is the thing that made the two race fixtures flaky: the
run's first delivery no longer 500s on a run row that has not landed
yet, so it no longer costs the ~5s the world waits before redelivering,
which is the entire budget those two tests measure against.

`resilient start` itself still fails, one layer deeper, and the reason
string now says which layer. The run *is* bootstrapped from
`run_started`; it is then rejected by its own model:

    2 validation errors for NonFinalWorkflowRun
    input.bytes / input.str
      input_value={'__type': 'Uint8Array', … 'ZGV2bFtbMV0sMTIzXQ=='}

That base64 is `devl[[1],123]` — the fixture's own input.
`RunInput.input` is typed `Any` (`world.py:117`), so the `Uint8Array`
envelope the local world's JSON queue transport wraps `bytes` in is
never decoded, and `NonFinalWorkflowRun.input` (`world.py:220`) wants
`bytes | str`. Local world only: the Vercel world's CBOR transport
carries bytes natively, and the stack's own unit tests use fakes with no
transport at all. So this lane is the only place it is visible, which is
what the lane is for.

Two things the upstream rebase will also need, noted here because this
one hit them: the stack's test doubles predate the streaming API and
fail to instantiate (`FakeWorld` and `_World` are missing `streams_get`,
`streams_write`, `streams_close`, `streams_list`, `streams_get_info`,
`streams_get_chunks`), 18 unit tests' worth, all one cause.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fantix and others added 2 commits August 12, 2026 16:34
It was true when written and stopped being true when the first hook
fixture reached the Vercel lane. `encp` is not only the format one run
uses to write to another — it is what every hook payload arrives as
there, because the driver resumes from outside the run with no symmetric
key, so `@workflow/world-vercel` seals to the run's public key instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`2edb224` is the stack rebased onto a main that already carries the
streaming API, so it no longer needs the throwaway branch this repo was
pinned to. `26 passed | 111 skipped` → `27 passed | 110 skipped`.

`resilient start: addTenWorkflow completes when run_created returns 500`
passes. That is the last of the three `unsupported` entries this defect
was costing, and the one written specifically to inject the condition —
so the exemption list is down from six to five and the run-row section of
the README is now history rather than a gap.

One of the stack's four commits exists because this lane found it:
`world-local` smuggles `bytes` through its JSON queue as a
`{__type: "Uint8Array"}` envelope, the file store already spoke that
dialect and the queue did not, and nothing noticed until
`runInput.input` became the first queue payload to carry bytes. Upstream
`d966b24`. Only this lane could see it — the Vercel world's queue is CBOR
and carries bytes natively, and the stack's unit tests build payloads
with no transport at all.

Also narrows gap 16 with an experiment the stable base made cheap.
`hookWithSleepWorkflow`'s stall does **not** need the concurrent
`sleep('1d')`: deleting that line changes nothing, the log is still
`hook_created`, `hook_received`, then no `step_created`. Together with
`sleepWithSequentialStepsWorkflow` passing (a pending wait does not block
step creation) and `hookWithSleepFinalStepWorkflow` passing (a step
created after two `hook_received` events is fine), what is left is one
shape: a step created after one `hook_received` in a body that would then
re-await the same hook. The reason string in `e2e-conformance.json` now
says exactly that, and says it is an observed shape rather than a root
cause.

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

socket-security Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedpypi/​vercel@​0.9.0 ⏵ 0.10.097 +1100100100100
Updatedpypi/​uvicorn@​0.52.1 ⏵ 0.52.398 +1100100100100
Updatedpypi/​vercel-queue@​0.7.3 ⏵ 0.8.0100 +1100100100100

View full report

…ecific

Repin to `93979e4`: the resilient-start stack has merged, and this is one
commit above `main` for `workflow: read sealed encp payloads`.

`encp` is what unblocks hooks on the deployed lane. Every hook payload
arrives sealed to the run's public key there, because the driver resumes
from outside the run with no symmetric key, so a runtime that cannot read
`encp` fails any hook fixture outright rather than skipping it. Upstream
derives the X25519 keypair from the same run key material `encr` already
resolves, so it needed no new key plumbing — the diff is two files, and
its 121 unit tests pass.

It buys zero tests here, and the reason is a worse finding than the one it
fixed. With `encp` out of the way `hookWithSleepFinalStepWorkflow` runs on
the Vercel lane, and locally it started flaking: a 120s timeout in 1 of 7
full-suite runs (5/5 in isolation, 6/6 after), with the same signature as
`hookWithSleepWorkflow`'s deterministic stall — a hook payload delivered
and the step that follows it never created.

That reframes the stall. It looked like a property of one body shape
("a step after one hook_received in a body that re-awaits the hook");
`hookWithSleepFinalStepWorkflow` differs only in that its step comes after
the *second* payload, so if it can fail too then both are the same race
and one of them merely usually wins it. Not confirmed — no event log was
captured for the failing run — and the reason string says so.

So the exemption stays, and it now costs more than a test: with every hook
fixture that carries a payload exempted, nothing in the suite exercises
`encp` at all. The fix for it rests on upstream's unit tests until the
stall is fixed, which makes that stall the thing standing between this
lane and verifying two upstream changes rather than one.

`27 passed | 110 skipped`, unchanged.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant