Skip to content

fix(core): write hook events alongside a suspension's steps, not ahead of them - #4392

Merged
pranaygp merged 4 commits into
mainfrom
process-hook-registrations-concurrently-with-ste
Sep 25, 2026
Merged

pranaygp merged 4 commits into
mainfrom
process-hook-registrations-concurrently-with-ste

Conversation

@pranaygp

@pranaygp pranaygp commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

What

The suspension handler used to write every hook event (hook_created, disposals, abort deliveries) before any step, wait, or attribute event, and the batched fan-out refused to engage at all when a suspension carried hook writes. A step created next to a hook therefore could not start until the hook write had landed. In eve's warm turn this is the ~108 ms "two hook_created writes, both done before the step can start" phase: every turn creates two AbortControllers, each registering a system hook, and turnStep waits behind both.

Hook writes now go out alongside the rest of the suspension:

  • Hooks run as one more op beside the step/wait/attribute writes. Ordering inside the hook writes is unchanged: code order per token (a dispose still flushes before a same-token recreate, a create still precedes its dispose), and aborts still follow the creations they target.
  • Forced creations (experimental_force) are no longer held ahead either, in the node:vm handler and the QuickJS entrypoint. A forced creation still publishes its victim's wake before its own token group's next write, but nothing else waits for it. The old barrier only protected crash recovery of that wake: the replay republishes an owed wake while the forced hook_created is the claimer's last own row (forcedCreationOwingWake). That check already missed step/wait terminals from other invocations landing in the same window, and a missed wake ends like a failed publish: the victim reads its takeover on its next invocation. Making the recovery independent of the log's tail is tracked in Force-claim victim wake recovery shouldn't depend on the claimer's log tail #4393. The forcedCreationOwingWake doc and the force-claim section of create-hook.mdx now describe the recovery as best-effort.
  • The batched fan-out engages alongside hook writes. Hooks can't ride createBatch (the server routes hook rows through the hooks home region and can't join them to the transaction), so they commit through the single path concurrently with the batch.
  • A lone inline step beside a hook create is pair-folded into the batch. The runtime already suppresses optimistic start when a suspension creates a hook, so the lazy step_started could neither overlap the body nor go out before the hook write committed. Folded, the step's claim commits concurrently with the hook create, so the body starts after max(hook, claim) instead of hook + claim. That is the eve case.

Semantics

As discussed, a step started in the same suspension as a hook can now begin before that hook is registered. A workflow that needs the guarantee (e.g. a step that hands the token to something that resumes it immediately) awaits hook.getConflict() first. create-hook.mdx has a new section on this, and create-webhook.mdx links to it.

The same applies to a hook conflict: the caller still observes the hook_conflict before it dispatches or runs anything from that pass, but a batched fan-out may already have published step messages for steps created beside the conflicting hook.

Supporting changes

  • Lazy-inline deferral is now decided from the queue's getConflict() awaiters, because the creates' outcomes are still in flight while the steps are written.
  • The hook create's inline delta is merged in slot order instead of appended. A concurrent step write may fold a skipped-slot report in while the hook create is in flight, and appending would put the delta's events after higher-slot ones. There's a test that fails with the old append.
  • Batch commits now count toward eventLogCarriedForward, so a hook delta is never taken as complete when a batch also wrote.
  • hookCreationMs (subtracted from TTFS) counts only the time hook writes held the suspension after every other write had settled. In a suspension that also writes attributes, none of that time is subtracted from a TTFS measurement that ends at the attr write, since it falls after the attr commit.
  • On a hook conflict, the runtime joins deferredBatchWork before moving on. If the suspension pre-claimed inline steps (now possible beside a hook), it replays in-process so owned recovery runs them, instead of re-invoking: a fresh delivery would find a live lease and park behind a backstop wake. A runtime test covers this with retained VM both on and off. With the fallback removed, the off case fails.

No server change is needed. workflow-server is untouched.

Tests

  • suspension-handler.test.ts: new tests for step/wait writes not waiting on a hook create (single path and batch), other writes not waiting on a forced creation's victim wake (the wake still goes out once, under its idempotency key), abort still after its creation, the lone-inline pair fold beside a hook (and no fold beside an already-created hook), the slot-ordered delta merge, carried-forward accounting with a batch, and hookCreationMs. The old "keeps the single path when the suspension carries hook writes" and "lands no other hook write between a forced creation and its victim wake" tests are replaced; quickjs-force-claim-wake.test.ts gets the QuickJS counterpart.
  • runtime.test.ts: hook conflict beside a pre-claimed inline step completes in-process with the body run once, parameterized over WORKFLOW_RETAINED_VM.
  • packages/core vitest run src: 2660 passed. docs-typecheck passes.

Docs Preview

Page v5
createHook: registering a hook before a step uses it link
createWebhook: returns (getConflict()) link
Batched event writes: runtime integration link
Worlds: WORKFLOW_BATCH_TRANSITIONS link

🤖 Generated with Claude Code

…d of them

The suspension handler committed every hook_created (and disposal/abort)
before it wrote any step, wait, or attribute event, and the batched fan-out
refused to engage for a suspension that carried hook writes at all. A step
created beside a hook, such as eve's turn step next to the system hooks its
AbortControllers register, could not start until those hook writes landed.

Hook writes now run as one more op beside the rest of the suspension's
writes. Ordering inside the hook writes is unchanged (code order per token,
creations before aborts), and forced creations keep their barrier ahead of
everything, since their victim-wake debt relies on being the log's tail.
The batched fan-out engages alongside hook writes, and a lone inline step
created beside a hook has its claim folded into that batch so it commits
concurrently with the hook create rather than after it.

Supporting changes:
- decide lazy-inline deferral from the queue's getConflict() awaiters, since
  the creates' outcomes are still in flight while steps are written
- merge the hook create's inline delta in slot order, as a concurrent write
  may have folded a skipped-slot report in while it was in flight
- count batch commits toward eventLogCarriedForward
- report hookCreationMs as only the time hook writes held the suspension
  beyond every other write
- on a hook conflict, join the deferred batch work, and replay in-process
  when the suspension pre-claimed inline steps this delivery now owns

A workflow that needs a hook registered before a step runs awaits
hook.getConflict() first; the docs now say so.

Co-Authored-By: Claude <noreply@anthropic.com>

Co-Authored-By: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>
@pranaygp
pranaygp requested a review from a team as a code owner September 24, 2026 22:38
Copilot AI lite review requested due to automatic review settings September 24, 2026 22:38
@changeset-bot

changeset-bot Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d291fd6

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

This PR includes changesets to release 16 packages
Name Type
@workflow/core Patch
@workflow/builders Patch
@workflow/cli Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/vitest Patch
@workflow/web-shared Patch
@workflow/web Patch
workflow Patch
@workflow/world-testing 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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@vercel

vercel Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

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

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

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

commit d291fd6 · Thu, 24 Sep 2026 23:45:21 GMT · run logs

Backend: vercel · app: nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 2086 (+49%) 🔻 2184 🔴 (+22%) 🔻 2230 🔴 (+23%) 🔻 2323 🔴 (+28%) 🔻 30
TTFS stream 1923 (+907%) 🔻 2184 🔴 (+27%) 🔻 2211 🔴 (+27%) 🔻 2280 🔴 (+27%) 🔻 30
TTFS hook + stream 2363 (+20%) 🔻 2479 🔴 (+18%) 🔻 2524 🔴 (+16%) 🔻 2587 🔴 (-26%) 💚 30
Fan-out TTFS Promise.all(100 steps) 588 (+48%) 🔻 1026 (+56%) 🔻 1260 (+84%) 🔻 2606 (+7.6%) 10
Fan-out TTLS Promise.all(100 steps) 2076 (-1.6%) 3869 (-18%) 💚 6395 (-23%) 💚 12790 (+42%) 🔻 10
STSO 1020 steps (inline) 129 (+18%) 🔻 150 (+11%) 166 (+11%) 198 (-5.7%) 1019
WO 1020 steps 150089 (+11%) 150089 (+11%) 150089 (+11%) 150089 (+11%) 1
CRTT first chunk (pooled) 66 (+12%) 100 (-15%) 112 (-50%) 💚 137 (-75%) 💚 28

Streams

Scenario CRTT 1st p75 p90 p99 CDV max iters
paced control (100/s, 60B) 95 (+1%) 139 (-83%) 208 (-80%) 438 (-86%) 109 (-67%) 10
size sweep (100/s, 160B-12KB) 92 (+5%) 135 (+2%) 184 (+5%) 374 (+21%) 121 (+5%) 10
replay gateway-gpt-5.4-nano-2000t (1x) 93 (-9%) 120 (-71%) 144 (-93%) 286 (-92%) 134 (-68%) 3
replay eve-gpt-5.6-sol-2000t (1x) 100 (+20%) 127 (-18%) 148 (-31%) 271 (-41%) 125 (-67%) 2
replay eve-gpt-5.6-sol-2000t (2x) 86 (-31%) 142 (-59%) 166 (-66%) 231 (-79%) 139 (-52%) 3
📈 STSO distribution vs main (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: main 135281ms → this run 149881ms (Δ +14600ms, +11%)

100-150 ms  ███████████████████┃████  main 915  this 763  -152
150-200 ms  ██░░░┃                    main  92  this 247  +155
200-250 ms  ┃                         main   8  this   8    +0
250-300 ms  ┃                         main   3  this   0    -3
300-350 ms  ┃                         main   0  this   1    +1
350-400 ms  ┃                         main   1  this   0    -1
📈 CRTT drill-down vs main (RTT distributions & profiles)
variant  RTT 1ms→5s+             avg         p50         p90         p99     n
control  ······▅█▁····  119.6 (-69%)  109 (-11%)  208 (-80%)  438 (-86%)  3000
sweep    ······▅█▁····   117.7 (+6%)  112 (+10%)   184 (+5%)  374 (+21%)  3000
gw 1x    ·····▁██▁····  104.4 (-62%)  101 (-12%)  144 (-93%)  286 (-92%)  5295
eve 1x   ·····▁▇█▁····  107.4 (-18%)   103 (-7%)  148 (-31%)  271 (-41%)  5186
eve 2x   ·····▁▄█▁····  119.8 (-39%)  115 (-12%)  166 (-66%)  231 (-79%)  7779

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

control  ▃▃█▃▃▁▁▃▂▃  111–143ms
sweep    █▃▂▆▂▃▇▁▁▃  111–129ms
gw 1x    ▆█▆▃▁▄▃▇▂▄  99–110ms
eve 1x   ▄▁▅▂▁▃█▆▅▃  98–122ms
eve 2x   ▃▁▄▃▂▆▆██▃  107–134ms

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

sweep  ▂▃▆██▄▁  116–120ms

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

control  ▅▇▆▇▇▇▁█▃▂  33–39ms
sweep    ▄▅▁▆██▇▃▃▆  43–54ms
gw 1x    █▅▅▁▄▃▄▇▂▆  27–34ms
eve 1x   █▄▇▁▆▅▅▆▄▅  19–23ms
eve 2x   ▇▆▃▃▇▅▆▁▄█  16–24ms
ℹ️ Metric definitions & methodology

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

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

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

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

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

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

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

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

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

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

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

❌ Some tests failed

❌ Failed E2E Tests

▲ Vercel Production (62 failed)

python-node (62 failed):

  • promiseAllWorkflow | wrun_41M3AVQB2N0GM8R2NK6G8ESV3M | 🔍 observability
  • promiseRaceWorkflow | wrun_41M3AVQB2N0GM8R2NK6G8ESV3N | 🔍 observability
  • promiseAnyWorkflow | wrun_41M3AVQB2N0GM8R2NK6G8ESV3P | 🔍 observability
  • retainedInterleavingWorkflow | wrun_41M3AVQKEE0GJH0PD390JH92CK | 🔍 observability
  • hookWorkflow | wrun_41M3AVQQXY0GRYNWNBJE8774K8 | 🔍 observability
  • hookWorkflow is not resumable via public webhook endpoint | wrun_41M3AVQRD80GXH58ZM79QX93R7 | 🔍 observability
  • sleepingWorkflow | wrun_41M3AVR0610GTM8NWRYG13BKN5 | 🔍 observability
  • parallelSleepWorkflow | wrun_41M3AVR1V60GJ9KBD6CZY3JSKM | 🔍 observability
  • sleepWinsRaceWorkflow | wrun_41M3AVR28N0GQY4Q7XFF7HWJ4Q | 🔍 observability
  • stepWinsRaceWorkflow | wrun_41M3AVR6P60GKNS1A9JVZ7NPCH | 🔍 observability
  • nullByteWorkflow | wrun_41M3AVR8820GR183FMYC81KYXG | 🔍 observability
  • outputStreamWorkflow no startIndex (reads all chunks)
  • outputStreamWorkflow positive startIndex (skips first chunk)
  • outputStreamWorkflow negative startIndex (reads from end)
  • outputStreamWorkflow - getTailIndex and getChunks getTailIndex returns correct index after stream completes
  • outputStreamWorkflow - getTailIndex and getChunks getChunks returns same content as reading the stream
  • outputStreamInsideStepWorkflow - getWritable() called inside step functions | wrun_41M3AVRQ4J0GWFH9P5S23T64KF | 🔍 observability
  • utf8StreamWorkflow | wrun_41M3AVRTF30GWG5J93CTD0QZRD | 🔍 observability
  • writableForwardedFromWorkflowWorkflow | wrun_41M3AVRX9T0GVGGH5BRRCSQ6VJ | 🔍 observability
  • writableForwardedFromStepWorkflow | wrun_41M3AVRZ4A0GSM9AMGEN2E9FQX | 🔍 observability
  • promiseRaceStressTestWorkflow | wrun_41M3AVS1CH0GGV20FG0S2XJZ87 | 🔍 observability
  • error handling retry behavior regular Error retries until success
  • error handling retry behavior FatalError fails immediately without retries
  • error handling retry behavior RetryableError respects custom retryAfter delay
  • error handling retry behavior maxRetries=0 disables retries
  • error handling catchability FatalError can be caught and detected with FatalError.is()
  • error handling catchability step throw round-trips FatalError with cause chain to workflow catch
  • error handling catchability workflow throw round-trips FatalError + cause through run_failed event
  • error handling not registered StepNotRegisteredError fails the step but workflow can catch it
  • error handling not registered StepNotRegisteredError fails the run when not caught in workflow
  • hookCleanupTestWorkflow - hook token reuse after workflow completion | wrun_41M3AVTJ2M0GGNZV5W4Y6NFBJA | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M3AVTBEZ0GSJ0V3WQVTAG6AX | 🔍 observability
  • hookGetConflictWorkflow - awaiting hook.getConflict() registers hook without payload | wrun_41M3AVTEQ40GNJ7VW2PAKXMFSE | 🔍 observability
  • 'hookGetConflictWithPriorStepWorkflow' - hook.getConflict() does not block step execution | wrun_41M3AVTGAQ0GRXG1C9JQZMZ93Y | 🔍 observability
  • 'hookGetConflictWithParallelStepWorkfl…' - hook.getConflict() does not block step execution | wrun_41M3AVTGMK0GNPWJ0TGE3P8V1M | 🔍 observability
  • hookGetConflictThenStepParallelWorkflow - hook.getConflict() continuation step runs alongside other steps | wrun_41M3AVTH0E0GKCD5P9YX38VM4A | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M3AVTR0Z0GPX7DFH7FKPCFZP | 🔍 observability
  • hookClaimOnlyMutexWorkflow - hook works as a pure run mutex without payload data | wrun_41M3AVV9DJ0GSHAGC2JR0JJZTD | 🔍 observability
  • hookAdoptOwnerResultWorkflow - duplicate adopts the owner result via conflict.returnValue | wrun_41M3AVV22N0GPQBWGZQ9NHVF78 | 🔍 observability
  • hookSignalOwnerWorkflow - duplicate forwards its payload to the owner via resumeHook | wrun_41M3AVV3110GY0X9X2H3MZV2NZ | 🔍 observability
  • resume-or-start route pattern - resumeHook retried after start() reaches the new run | wrun_41M3AVVWBM0GKRDDVK315XGWA2 | 🔍 observability
  • hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running | wrun_41M3AVVY7A0GXJF8X0NG9Z7BW8 | 🔍 observability
  • hookTokenReuseLoopWorkflow - same run recreates a hook with the same token after dispose() | wrun_41M3AVVXF20GX3MK9SKS0GB6SP | 🔍 observability
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step | wrun_41M3AVWJA80GMZAYM7B4BJPX8N | 🔍 observability
  • cancelRun - cancelling a running workflow | wrun_41M3AVXGEQ0GX4CAPGT8WXDCZ9 | 🔍 observability
  • cancelRun via CLI - cancelling a running workflow | wrun_41M3AVXJ5S0GV3RXENJ40WVER3 | 🔍 observability
  • hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep | wrun_41M3AVXKP30GGNZ6WXKB50YHQT | 🔍 observability
  • hookWithSleepFinalStepWorkflow - step only on final payload | wrun_41M3AVXS3B0GGNB5DAZ3TW3BNJ | 🔍 observability
  • sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration | wrun_41M3AVXSD80GY0PTTYVNBDJ683 | 🔍 observability
  • sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) | wrun_41M3AVXT3Z0GKGDAV99B1H1T0R | 🔍 observability
  • AbortController abortTimeoutWorkflow: timeout cancels long-running step
  • AbortController abortParallelWorkflow: abort cancels all parallel steps
  • AbortController abortReasonWorkflow: abort reason preserved across boundaries
  • AbortController abortViaHookWorkflow: external hook triggers abort on in-flight step
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_41M3AVY7250GVGKCG7HQ3HG834 | 🔍 observability
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41M3AVY7YD0GR9ZHZZXA6XN941 | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes Promise.all of disjoint-key writes: every key lands
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run

⚠️ Flaky E2E Tests (passed on retry)

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

  • single tool call (sveltekit · vercel-prod / vercel / node / production)
  • string instructions are passed to the model (nextjs-webpack · vercel-prod / vercel / node / production)

🛠 Infra Events (absorbed by the harness)

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

122 infra events
  • cold-start-warmup · suite warmup (python) · at 23:40:09Z · abandoned wrun_41M3AWH7NS0GP3Q8S919NFJ5RJ · (+7 more)
  • run-pickup-stall · retainedInterleavingWorkflow (python) · at 23:40:25Z · abandoned wrun_41M3AWMWFM0GHZSJ70N18P2HEV
  • run-pickup-stall · promiseAllWorkflow (python) · at 23:40:25Z · abandoned wrun_41M3AWMWFM0GHZSJ70N18P2HER
  • run-pickup-stall · promiseRaceWorkflow (python) · at 23:40:25Z · abandoned wrun_41M3AWMWFM0GHZSJ70N18P2HES
  • run-pickup-stall · hookWorkflow (python) · at 23:40:25Z · abandoned wrun_41M3AWMWFS0GM9YSAVKNKFAN7G
  • run-pickup-stall · promiseAnyWorkflow (python) · at 23:40:25Z · abandoned wrun_41M3AWMWFM0GHZSJ70N18P2HET
  • run-pickup-stall · hookWorkflow (python) · at 23:41:12Z · abandoned wrun_41M3AWPAJ50GN8VSSQYCBQFYGP
  • run-pickup-stall · retainedInterleavingWorkflow (python) · at 23:41:13Z · abandoned wrun_41M3AWPAE20GQ01EKFEG6GTWXZ
  • run-pickup-stall · promiseAllWorkflow (python) · at 23:41:26Z · abandoned wrun_41M3AWPQPZ0GGXNTDYEJTWGXNP
  • run-pickup-stall · promiseAnyWorkflow (python) · at 23:41:26Z · abandoned wrun_41M3AWPQVF0GJSKZA3GP0TCNHE
  • run-pickup-stall · promiseRaceWorkflow (python) · at 23:41:26Z · abandoned wrun_41M3AWPQSQ0GXVJ248GGPYNBE0
  • run-pickup-stall · hookWorkflow is not resumable via public webhook endpoint (python) · at 23:41:59Z · abandoned wrun_41M3AWQR800GQWKVD2Q0B2VBH7
  • run-pickup-stall · sleepingWorkflow (python) · at 23:42:00Z · abandoned wrun_41M3AWQRGX0GTV793JNN25WA6C
  • run-pickup-stall · parallelSleepWorkflow (python) · at 23:42:28Z · abandoned wrun_41M3AWRKFX0GMWS8KJX67CMFDE
  • run-pickup-stall · sleepWinsRaceWorkflow (python) · at 23:42:28Z · abandoned wrun_41M3AWRKNB0GRE3SJN303AZP4E
  • run-pickup-stall · stepWinsRaceWorkflow (python) · at 23:42:50Z · abandoned wrun_41M3AWRKVP0GPP6T7PH76ZZEWR
  • run-pickup-stall · sleepingWorkflow (python) · at 23:44:03Z · abandoned wrun_41M3AWTA470GH3V86GTWDM1CJN
  • run-pickup-stall · hookWorkflow is not resumable via public webhook endpoint (python) · at 23:44:03Z · abandoned wrun_41M3AWTA8Z0GMFNJZ1C20QC7NW
  • run-pickup-stall · sleepWinsRaceWorkflow (python) · at 23:44:03Z · abandoned wrun_41M3AWVBHX0GWKV6N5BZGBEA7F
  • run-pickup-stall · parallelSleepWorkflow (python) · at 23:44:03Z · abandoned wrun_41M3AWVBCE0GJ0EB2KMPH4C5PR
  • run-pickup-stall · stepWinsRaceWorkflow (python) · at 23:44:04Z · abandoned wrun_41M3AWVBR70GY82C8AMRNQXVBB
  • run-pickup-stall · nullByteWorkflow (python) · at 23:44:24Z · abandoned wrun_41M3AWW5EF0GNAEV4P8H5FXG32
  • run-pickup-stall · no startIndex (reads all chunks) (python) · at 23:44:24Z · abandoned wrun_41M3AWW5FN0GHJKKGSJGCWAJFR
  • run-pickup-stall · positive startIndex (skips first chunk) (python) · at 23:44:57Z · abandoned wrun_41M3AWX5ZF0GMNPTJJEABY72VD
  • run-pickup-stall · negative startIndex (reads from end) (python) · at 23:44:57Z · abandoned wrun_41M3AWX64W0GNWSN8QA8XTNNJF
  • run-pickup-stall · getTailIndex returns correct index after stream completes (python) · at 23:45:00Z · abandoned wrun_41M3AWX8FR0GWEG0WG5M0XP10K
  • run-pickup-stall · getTailIndex returns -1 before any chunks are written (python) · at 23:45:24Z · abandoned wrun_41M3AWY01F0GQSCRNDSVNMJTW9
  • run-pickup-stall · getChunks returns same content as reading the stream (python) · at 23:45:24Z · abandoned wrun_41M3AWY02N0GZWVBSBD1B5K196
  • run-pickup-stall · outputStreamInsideStepWorkflow - getWritable() called inside step functions (python) · at 23:45:41Z · abandoned wrun_41M3AWYGHF0GVZQK89WS0Q02R7
  • run-pickup-stall · utf8StreamWorkflow (python) · at 23:45:57Z · abandoned wrun_41M3AWZ0JG0GSJKRA90B9PV7VH
  • run-pickup-stall · nullByteWorkflow (python) · at 23:46:00Z · abandoned wrun_41M3AWZ32R0GN7FVBGGD1XVW22
  • run-pickup-stall · no startIndex (reads all chunks) (python) · at 23:46:00Z · abandoned wrun_41M3AWZ3GK0GXD9RC5BH9JF262
  • run-pickup-stall · positive startIndex (skips first chunk) (python) · at 23:46:24Z · abandoned wrun_41M3AWZTNN0GGCAJVPR98DSFFV
  • run-pickup-stall · negative startIndex (reads from end) (python) · at 23:46:41Z · abandoned wrun_41M3AX0BSA0GJP08MDQ0511E57
  • run-pickup-stall · getTailIndex returns correct index after stream completes (python) · at 23:47:00Z · abandoned wrun_41M3AX0YA20GX9D3304DKJTFRX
  • run-pickup-stall · getChunks returns same content as reading the stream (python) · at 23:47:02Z · abandoned wrun_41M3AX104C0GRHBFDR609PQP0V
  • run-pickup-stall · outputStreamInsideStepWorkflow - getWritable() called inside step functions (python) · at 23:47:24Z · abandoned wrun_41M3AX1N8N0GT3W0XCMG95KMT9
  • run-pickup-stall · writableForwardedFromWorkflowWorkflow (python) · at 23:47:41Z · abandoned wrun_41M3AX26C90GZ6NECMNXTF9GQH
  • run-pickup-stall · writableForwardedFromStepWorkflow (python) · at 23:47:59Z · abandoned wrun_41M3AX2QPS0GSYE0FRVPVX323D
  • run-pickup-stall · utf8StreamWorkflow (python) · at 23:48:00Z · abandoned wrun_41M3AX2RX30GYM8WKN3HQYMA28
  • run-pickup-stall · promiseRaceStressTestWorkflow (python) · at 23:48:04Z · abandoned wrun_41M3AX2W8G0GTV42VAETH7HAXF
  • run-pickup-stall · regular Error retries until success (python) · at 23:48:24Z · abandoned wrun_41M3AX3GEQ0GRPAJG7T6Q93DK4
  • run-pickup-stall · FatalError fails immediately without retries (python) · at 23:49:04Z · abandoned wrun_41M3AX4PVG0GXPRTY5BAD76RQT
  • run-pickup-stall · RetryableError respects custom retryAfter delay (python) · at 23:49:24Z · abandoned wrun_41M3AX5B1R0GVJMC6Z60ANQWW3
  • run-pickup-stall · maxRetries=0 disables retries (python) · at 23:49:41Z · abandoned wrun_41M3AX5VJA0GSM1ETY9VPCZ5Z6
  • run-pickup-stall · FatalError can be caught and detected with FatalError.is() (python) · at 23:49:59Z · abandoned wrun_41M3AX6CWS0GZ6ECDMTDDB7PDT
  • run-pickup-stall · step throw round-trips FatalError with cause chain to workflow catch (python) · at 23:50:00Z · abandoned wrun_41M3AX6E330GY4SVGFGY29CWB9
  • run-pickup-stall · workflow throw round-trips FatalError + cause through run_failed event (python) · at 23:50:04Z · abandoned wrun_41M3AX6HEG0GHB1RPW5W18XQBW
  • run-pickup-stall · StepNotRegisteredError fails the step but workflow can catch it (python) · at 23:50:24Z · abandoned wrun_41M3AX75MS0GKPVSYP37J11M7P
  • run-pickup-stall · StepNotRegisteredError fails the run when not caught in workflow (python) · at 23:50:42Z · abandoned wrun_41M3AX7P5A0GK146209972DSWQ
  • run-pickup-stall · promiseRaceStressTestWorkflow (python) · at 23:51:58Z · abandoned wrun_41M3AX9GR90GHX052AACPJD84J
  • run-pickup-stall · regular Error retries until success (python) · at 23:51:59Z · abandoned wrun_41M3AX9J4B0GKV5D3HVNSZ54CZ
  • run-pickup-stall · writableForwardedFromWorkflowWorkflow (python) · at 23:52:00Z · abandoned wrun_41M3AX9JKQ0GNDMN67D44D8DHZ
  • run-pickup-stall · writableForwardedFromStepWorkflow (python) · at 23:52:18Z · abandoned wrun_41M3AX9K9R0GWY3JPYNE2HMCMZ
  • run-pickup-stall · hookCleanupTestWorkflow - hook token reuse after workflow completion (python) · at 23:52:47Z · abandoned wrun_41M3AXAETT0GGMH4JES9PAH62Y
  • run-pickup-stall · FatalError fails immediately without retries (python) · at 23:53:11Z · abandoned wrun_41M3AXBBBB0GKRFQNDXF0WK114
  • run-pickup-stall · RetryableError respects custom retryAfter delay (python) · at 23:53:11Z · abandoned wrun_41M3AXBCQB0GQH9M3HCFYJZ0N0
  • run-pickup-stall · maxRetries=0 disables retries (python) · at 23:53:50Z · abandoned wrun_41M3AXD5YA0GXH22TMKFJ8E7GE
  • run-pickup-stall · FatalError can be caught and detected with FatalError.is() (python) · at 23:53:50Z · abandoned wrun_41M3AXD6QA0GHQY8PX6D4EPR0H
  • run-pickup-stall · workflow throw round-trips FatalError + cause through run_failed event (python) · at 23:53:51Z · abandoned wrun_41M3AXD7SP0GX622BE3SRJP0WX
  • run-pickup-stall · step throw round-trips FatalError with cause chain to workflow catch (python) · at 23:53:51Z · abandoned wrun_41M3AXD7AB0GPDEX8WJA7N57QA
  • run-pickup-stall · StepNotRegisteredError fails the step but workflow can catch it (python) · at 23:53:51Z · abandoned wrun_41M3AXD8FR0GZTQRB3Q7ZMR5ND
  • run-pickup-stall · StepNotRegisteredError fails the run when not caught in workflow (python) · at 23:54:43Z · abandoned wrun_41M3AXF1XB0GSNPTA0VG2ZFGW3
  • run-pickup-stall · hookCleanupTestWorkflow - hook token reuse after workflow completion (python) · at 23:54:44Z · abandoned wrun_41M3AXF36C0GGR14XBW1Q9G9D6
  • run-pickup-stall · hookGetConflictWorkflow - awaiting hook.getConflict() registers hook without payload (python) · at 23:54:44Z · abandoned wrun_41M3AXF3Q80GK2PK3AS3AMXCDK
  • run-pickup-stall · 'hookGetConflictWithPriorStepWorkflow' - hook.getConflict() does not block step execution (python) · at 23:54:45Z · abandoned wrun_41M3AXF47N0GM26FTP2SV0HB04
  • run-pickup-stall · 'hookGetConflictWithParallelStepWorkfl…' - hook.getConflict() does not block step execution (python) · at 23:55:31Z · abandoned wrun_41M3AXGH8X0GT5RWQDWPFATBY1
  • run-pickup-stall · hookGetConflictThenStepParallelWorkflow - hook.getConflict() continuation step runs alongside other steps (python) · at 23:55:43Z · abandoned wrun_41M3AXGX4Q0GWMNZJJFWQHSF50
  • run-pickup-stall · hookGetConflictWorkflow - awaiting hook.getConflict() registers hook without payload (python) · at 23:55:45Z · abandoned wrun_41M3AXGZ420GHZBQ8A95N0XJJK
  • run-pickup-stall · 'hookGetConflictWithPriorStepWorkflow' - hook.getConflict() does not block step execution (python) · at 23:55:46Z · abandoned wrun_41M3AXGZEP0GG2P3C4YFHC8XTM
  • run-pickup-stall · hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered (python) · at 23:55:46Z · abandoned wrun_41M3AXH04K0GVY4674NJZNV9YM
  • run-pickup-stall · 'hookGetConflictWithParallelStepWorkfl…' - hook.getConflict() does not block step execution (python) · at 23:56:32Z · abandoned wrun_41M3AXJCGA0GP8CPFZ3XGGYFY4
  • run-pickup-stall · hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered (python) · at 23:56:34Z · abandoned wrun_41M3AXJE9T0GNKGZHVWXWPAESN
  • run-pickup-stall · hookClaimOnlyMutexWorkflow - hook works as a pure run mutex without payload data (python) · at 23:56:46Z · abandoned wrun_41M3AXJTH70GJ5STEXPE6MVQAR
  • run-pickup-stall · hookAdoptOwnerResultWorkflow - duplicate adopts the owner result via conflict.returnValue (python) · at 23:56:46Z · abandoned wrun_41M3AXJTRA0GWM53G07FQE9983
  • run-pickup-stall · hookGetConflictThenStepParallelWorkflow - hook.getConflict() continuation step runs alongside other steps (python) · at 23:57:14Z · abandoned wrun_41M3AXKNM80GZZ3795WJQ63AXY
  • run-pickup-stall · hookSignalOwnerWorkflow - duplicate forwards its payload to the owner via resumeHook (python) · at 23:57:21Z · abandoned wrun_41M3AXKW9W0GZ8E95W7BM9CDEA
  • run-pickup-stall · resume-or-start route pattern - resumeHook retried after start() reaches the new run (python) · at 23:57:33Z · abandoned wrun_41M3AXM7Z30GY69E1RMRPX7WAF
  • run-pickup-stall · hookAdoptOwnerResultWorkflow - duplicate adopts the owner result via conflict.returnValue (python) · at 23:57:33Z · abandoned wrun_41M3AXM8F10GS9R35Q7FQJVR16
  • run-pickup-stall · hookClaimOnlyMutexWorkflow - hook works as a pure run mutex without payload data (python) · at 23:57:33Z · abandoned wrun_41M3AXM8B20GNTEN96M7EP7BF7
  • run-pickup-stall · hookSignalOwnerWorkflow - duplicate forwards its payload to the owner via resumeHook (python) · at 23:58:07Z · abandoned wrun_41M3AXN9YQ0GHXBFPN6XAYVT11
  • run-pickup-stall · resume-or-start route pattern - resumeHook retried after start() reaches the new run (python) · at 23:58:20Z · abandoned wrun_41M3AXNP520GH3NVFSFRRPTNN7
  • run-pickup-stall · hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running (python) · at 23:58:20Z · abandoned wrun_41M3AXNP6T0GG8ZC2R7KS8DD14
  • run-pickup-stall · hookTokenReuseLoopWorkflow - same run recreates a hook with the same token after dispose() (python) · at 23:58:20Z · abandoned wrun_41M3AXNP6W0GK5YW5XCNJSEW6A
  • run-pickup-stall · spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step (python) · at 23:58:44Z · abandoned wrun_41M3AXPE3S0GTNVMVCV72PRH5R
  • run-pickup-stall · cancelRun - cancelling a running workflow (python) · at 23:59:01Z · abandoned wrun_41M3AXPXSC0GJEB8NBQRTS6F4H
  • run-pickup-stall · cancelRun via CLI - cancelling a running workflow (python) · at 23:59:07Z · abandoned wrun_41M3AXQ3WF0GPKM1HJ6YPHNBKS
  • run-pickup-stall · hookTokenReuseLoopWorkflow - same run recreates a hook with the same token after dispose() (python) · at 23:59:07Z · abandoned wrun_41M3AXQ3V50GQVDHGWSN31WYA1
  • run-pickup-stall · hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running (python) · at 23:59:07Z · abandoned wrun_41M3AXQ4680GX1005D9FZD0K0Z
  • run-pickup-stall · cancelRun - cancelling a running workflow (python) · at 23:59:33Z · abandoned wrun_41M3AXQWYH0GVG61W1XCRQJ6EF
  • run-pickup-stall · cancelRun via CLI - cancelling a running workflow (python) · at 23:59:39Z · abandoned wrun_41M3AXR33Z0GRSAJPMG6KGE3CC
  • run-pickup-stall · hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep (python) · at 23:59:54Z · abandoned wrun_41M3AXRHN10GHGYPE5VCFN6Z9D
  • run-pickup-stall · hookWithSleepFinalStepWorkflow - step only on final payload (python) · at 23:59:54Z · abandoned wrun_41M3AXRJ2W0GVYR680FK4QC7P2
  • run-pickup-stall · sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration (python) · at 00:00:05Z · abandoned wrun_41M3AXRW1K0GXVZ03Y21DT1APB
  • run-pickup-stall · sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) (python) · at 00:00:11Z · abandoned wrun_41M3AXS2CE0GSWGZF430CRQDM4
  • run-pickup-stall · hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep (python) · at 00:00:41Z · abandoned wrun_41M3AXSZKT0GGFS8CGJR7VQKVN
  • run-pickup-stall · hookWithSleepFinalStepWorkflow - step only on final payload (python) · at 00:00:41Z · abandoned wrun_41M3AXSZV90GYJYWCQB06EB58T
  • run-pickup-stall · spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step (python) · at 00:00:45Z · abandoned wrun_41M3AXT41V0GM1KE76PETZYGFK
  • run-pickup-stall · sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration (python) · at 00:01:05Z · abandoned wrun_41M3AXTQ7T0GWXRTCNP650BZCR
  • run-pickup-stall · sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) (python) · at 00:01:12Z · abandoned wrun_41M3AXTXMM0GJTWTT6ATVT2N8P
  • run-pickup-stall · abortTimeoutWorkflow: timeout cancels long-running step (python) · at 00:01:29Z · abandoned wrun_41M3AXVDYG0GJBMNCP2GAT9HAX
  • run-pickup-stall · abortParallelWorkflow: abort cancels all parallel steps (python) · at 00:01:29Z · abandoned wrun_41M3AXVE270GTPG0FZMFV1JAYQ
  • run-pickup-stall · abortReasonWorkflow: abort reason preserved across boundaries (python) · at 00:02:24Z · abandoned wrun_41M3AXWHTV0GV103P6SNW0DE3X
  • run-pickup-stall · metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (Vercel World Regression: getWorkflowMetadata() fully breaking runs #1577) (python) · at 00:02:42Z · abandoned wrun_41M3AXWR7M0GH0NTF00R0264TW
  • run-pickup-stall · abortViaHookWorkflow: external hook triggers abort on in-flight step (python) · at 00:02:53Z · abandoned wrun_41M3AXX8N70GTNA142R5GPN4CS
  • run-pickup-stall · abortTimeoutWorkflow: timeout cancels long-running step (python) · at 00:04:01Z · abandoned wrun_41M3AXZ9QC0GM5GTGY5HDNGRWH
  • run-pickup-stall · abortParallelWorkflow: abort cancels all parallel steps (python) · at 00:04:03Z · abandoned wrun_41M3AXZY9V0GJ06RZKATJ5YK1M
  • run-pickup-stall · abortReasonWorkflow: abort reason preserved across boundaries (python) · at 00:04:04Z · abandoned wrun_41M3AY00HQ0GZ2NV2NZZWX1D5E
  • run-pickup-stall · metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (Vercel World Regression: getWorkflowMetadata() fully breaking runs #1577) (python) · at 00:04:08Z · abandoned wrun_41M3AY089H0GNKC3DXJZ4PS00E
  • run-pickup-stall · start: initial attributes are seeded on run creation (python) · at 00:04:15Z · abandoned wrun_41M3AY0H4C0GT4VDMJZ0V1FYJD
  • run-pickup-stall · start: reserved-prefix initial attributes are seeded with allowReservedAttributes (python) · at 00:04:35Z · abandoned wrun_41M3AY14AC0GKW3GJ1XH4SX9PQ
  • run-pickup-stall · setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly (python) · at 00:04:45Z · abandoned wrun_41M3AY1EDW0GZ0APK9ZG63BXNF
  • run-pickup-stall · setAttributesInsideStepWorkflow: step-body calls append attributed native events (python) · at 00:04:56Z · abandoned wrun_41M3AY1RWW0GT560G51PK02MHG
  • run-pickup-stall · abortViaHookWorkflow: external hook triggers abort on in-flight step (python) · at 00:04:58Z · abandoned wrun_41M3AY1V4R0GR2AP30DNK05JHW
  • run-pickup-stall · Promise.all of disjoint-key writes: every key lands (python) · at 00:05:07Z · abandoned wrun_41M3AY23G60GYFEC4K9PMKNR5F
  • run-pickup-stall · start: initial attributes are seeded on run creation (python) · at 00:05:16Z · abandoned wrun_41M3AY2CFE0GT7TAFJ96EWB295
  • run-pickup-stall · start: reserved-prefix initial attributes are seeded with allowReservedAttributes (python) · at 00:05:19Z · abandoned wrun_41M3AY2FAJ0GXYNA8PFCW8DK64
  • run-pickup-stall · setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly (python) · at 00:05:28Z · abandoned wrun_41M3AY2QQ40GTZVY3Q6NTF8DTJ
  • run-pickup-stall · setAttributesInsideStepWorkflow: step-body calls append attributed native events (python) · at 00:05:37Z · abandoned wrun_41M3AY30SQ0GWYRM3P010P713B
  • run-pickup-stall · Promise.all of disjoint-key writes: every key lands (python) · at 00:05:46Z · abandoned wrun_41M3AY39RF0GQK6FQGAM2ZEKWM
  • run-pickup-stall · workflow throws after awaited setAttributes: attribute still persists on the failed run (python) · at 00:05:47Z · abandoned wrun_41M3AY3ABZ0GM28W4A3Y8DVEY7
  • run-pickup-stall · workflow throws after awaited setAttributes: attribute still persists on the failed run (python) · at 00:06:17Z · abandoned wrun_41M3AY48900GX0Y2H9H4W88ZWN

E2E Test Summary

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

❌ ▲ Vercel Production

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

✅ 💻 Local Development

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

✅ 📦 Local Production

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

✅ 🐘 Local Postgres

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

✅ 🪟 Windows

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

✅ vercel-http-transport

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

✅ vercel-multi-region

App Passed Failed Skipped
✅ nextjs-turbopack 27 0 0

✅ vercel-ws-transport

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

📋 View full workflow run

@github-actions

github-actions Bot commented Sep 24, 2026 •

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

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

d291fd6 · run

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Sim World

Simulated world deterministic testing for races. Traces

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

fence=per-spec

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

Full trace: world-sim.txt

…m wake

Forced hook creations no longer run ahead of the rest of the suspension in
either engine. The barrier existed only so a crash before the victim wake
would leave the forced hook_created as the claimer's last own row, which is
how the replay detects an owed wake. That detection was already leaky (a
step or wait terminal from another invocation can land in the same window),
and a missed wake ends the same way as a failed publish: the victim reads
the takeover on its next invocation. Making the recovery independent of
the log's tail is tracked in #4393.

Co-Authored-By: Claude <noreply@anthropic.com>

Co-Authored-By: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>
- Docs: say that a step started beside a hook can run even when the hook
  conflicts, so a duplicate run that relies on HookConflictError rather than
  `hook.getConflict()` may already have started it (create-hook, idempotency).
- TTFS: snapshot the pre-attr hook time before adding the attr suspension's
  own `hookCreationMs`, which now falls after the attr commit that ends the
  measured window. Refresh the comments that still said the hook phase runs
  before the attr writes.
- Tests: a run that ends over the conflict leaves the claimed step unrun; a
  lost pair beside a conflict hands the run off only after the batch's
  trailing chunk and step publishes land; a hook with a getConflict() awaiter
  pre-claims nothing.
- Re-wrap a QuickJS entrypoint comment.

Co-Authored-By: Claude <noreply@anthropic.com>

Co-Authored-By: Peter Wielander <29887157+VaguelySerious@users.noreply.github.com>

@VaguelySerious VaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI review: no blocking issues

@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 35ebeb4 (AI decision).

Despite the fix(core) prefix, this is a latency optimization: it parallelizes hook event writes with a suspension's step/wait/attribute writes and folds a lone inline step into the batched fan-out, and it deliberately changes observable semantics (a step created beside a hook can now start before the hook is registered, and a forced creation's victim wake becomes best-effort rather than barrier-protected). It also builds on main-only infrastructure that stable does not have — packages/core/src/runtime/hook-wake.ts, step-latency.ts, quickjs-entrypoint.ts, and the batched event writes (createBatch/WORKFLOW_BATCH_TRANSITIONS) work are all absent from origin/stable. No user-visible defect is being repaired, so it does not belong on a maintenance line.

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

35ebeb4c5afa263bb37cd870a79c608fa237510a

This branch was successfully deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants