Skip to content

[world-local] Remove default queue delivery deadlines - #4351

Merged
VaguelySerious merged 2 commits into
mainfrom
peter/world-local-no-default-deadline
Sep 24, 2026
Merged

VaguelySerious merged 2 commits into
mainfrom
peter/world-local-no-default-deadline

Conversation

@VaguelySerious

@VaguelySerious VaguelySerious commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Defaults-only alternative to #4328 that fixes #3909.

Problem

Since #3255, world-local gives each queue delivery a 30s undici headersTimeout/bodyTimeout. A delivery runs inline steps before the handler responds, so any "use step" that takes longer than 30s has its live delivery treated as a transport failure, redelivered, and its body executed a second time while the first is still running. v4 world-local used no custom agent, so it got undici's 300s defaults.

Change

  • DEFAULT_HEADERS_TIMEOUT_MS and DEFAULT_BODY_TIMEOUT_MS go from 30_000 to 0 (no deadline), matching what fix(world-postgres): remove implicit queue HTTP deadlines #4114 did for world-postgres.
  • WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS / WORKFLOW_LOCAL_BODY_TIMEOUT_MS still work, now as an opt-in. Docs warn that a value below the longest inline step brings the duplication back.

This PR leaves out the runtime.ts single-flight change from #4328, so it is a lower-risk option for the v5 stable release. #4328 still covers worlds whose transport can redeliver a live message.

Tests

  • pnpm --filter @workflow/world-local test: 602 passed. typecheck clean.
  • The default test now checks that the default agent options equal the explicit 0 (disabled) configuration. The stalled-handler and stalled-body tests already set their deadlines explicitly, so they now cover the opt-in path.

🤖 Generated with Claude Code

A delivery runs inline steps before it responds, so the 30s headers/body
deadline redelivered live messages for any step longer than 30s and executed
the step a second time. WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS and
WORKFLOW_LOCAL_BODY_TIMEOUT_MS remain as opt-in deadlines.

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

changeset-bot Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2e457c6

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

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

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

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

@vercel

vercel Bot commented 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 2:34am UTC
example-nextjs-workflow-webpack Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC
example-workflow Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC
workbench-astro-workflow Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC
workbench-express-workflow Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC
workbench-fastify-workflow Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC
workbench-hono-workflow Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC
workbench-nestjs-workflow Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC
workbench-nitro-workflow Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC
workbench-nuxt-workflow Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC
workbench-python-workflow Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC
workbench-sveltekit-workflow Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC
workbench-tanstack-start-workflow Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC
workbench-vite-workflow Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC
workflow-docs Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC
workflow-swc-playground Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC
workflow-tarballs Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC
workflow-web Ready Ready Preview, v0 Sep 24, 2026 2:34am UTC

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

commit 2e457c6 · Thu, 24 Sep 2026 02:57:52 GMT · run logs

Backend: vercel · app: nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 2177 (+56%) 🔻 2316 🔴 (+33%) 🔻 2336 🔴 (+32%) 🔻 2450 🔴 (+15%) 🔻 30
TTFS stream 216 (-86%) 💚 2283 🔴 (+37%) 🔻 2310 🔴 (+37%) 🔻 2368 🔴 (+39%) 🔻 30
TTFS hook + stream 2473 (+36%) 🔻 2615 🔴 (+29%) 🔻 2644 🔴 (+28%) 🔻 2698 🔴 (+28%) 🔻 30
Fan-out TTFS Promise.all(100 steps) 493 (+10%) 2593 (+304%) 🔻 2600 (+29%) 🔻 2763 (+33%) 🔻 10
Fan-out TTLS Promise.all(100 steps) 1724 (-19%) 💚 3905 (+1.8%) 4049 (-7.8%) 10456 (+25%) 🔻 10
STSO 1020 steps (inline) 122 (+21%) 🔻 160 (+18%) 🔻 180 (+24%) 🔻 235 (-11%) 1019
WO 1020 steps 156539 (+16%) 🔻 156539 (+16%) 🔻 156539 (+16%) 🔻 156539 (+16%) 🔻 1
CRTT first chunk (pooled) 72 (+13%) 102 (+1.0%) 176 (+30%) 🔻 4012 (+731%) 🔻 28

Streams

Scenario CRTT 1st p75 p90 p99 CDV max iters
paced control (100/s, 60B) 93.5 (+7%) 147 (-5%) 264 (-11%) 3985 (+318%) 139 (-46%) 10
size sweep (100/s, 160B-12KB) 96.5 (+30%) 153 (-8%) 195 (-36%) 387 (-36%) 125 (-42%) 10
replay gateway-gpt-5.4-nano-2000t (1x) 95 (-2%) 133 (-4%) 158 (-23%) 279 (-50%) 290 (-43%) 3
replay eve-gpt-5.6-sol-2000t (1x) 124 (+82%) 132 (-13%) 161 (-31%) 279 (-16%) 218 (-37%) 2
replay eve-gpt-5.6-sol-2000t (2x) 102 (-5%) 149 (-18%) 188 (-22%) 362 (-18%) 220 (-19%) 3
📈 STSO distribution vs main (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: main 134799ms → this run 156291ms (Δ +21492ms, +16%)

100-150 ms  ███████████████┃████████  main 927  this 603  -324
150-200 ms  ██░░░░░░░┃                main  66  this 372  +306
200-250 ms  ┃                         main  13  this  38   +25
250-300 ms  ┃                         main   3  this   3    +0
300-350 ms  ┃                         main   7  this   2    -5
350-400 ms  ┃                         main   1  this   1    +0
400-450 ms  ┃                         main   1  this   0    -1
550-600 ms  ┃                         main   1  this   0    -1
📈 CRTT drill-down vs main (RTT distributions & profiles)
variant  RTT 1ms→5s+              avg         p50         p90           p99     n
control  ······▃█▁·▁▁·  371.5 (+159%)  120 (+10%)  264 (-11%)  3985 (+318%)  3000
sweep    ······▃█▁····    126.4 (-7%)   118 (+5%)  195 (-36%)    387 (-36%)  3000
gw 1x    ·····▁▅█▁····   114.4 (-11%)   107 (-4%)  158 (-23%)    279 (-50%)  5295
eve 1x   ·····▁▅█▁····    114.8 (-9%)   107 (±0%)  161 (-31%)    279 (-16%)  5186
eve 2x   ·····▁▃█▁····   129.5 (-11%)   123 (-2%)  188 (-22%)    362 (-18%)  7779

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

control  █▇▇▆▅▅▄▃▂▁  225–501ms
sweep    ▄▂▁▃█▅▄▃▁▃  117–145ms
gw 1x    █▂▅▄▁▂▃▅▃▇  106–125ms
eve 1x   ▄▂▇█▁██▃▅▆  106–121ms
eve 2x   ▃▁▂▂▁▂▆█▆▄  116–154ms

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

sweep  ██▅▁▂▄▆  123–129ms

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

control  ▁▂▆▇█▄▄▅▃▁  33–48ms
sweep    ▄▁▂▅█▃▄▃▁▅  46–64ms
gw 1x    ▅▃█▁▄▄█▆▂▇  31–39ms
eve 1x   ▄▅▄▄▁▅▄▂▄█  19–27ms
eve 2x   ▇▅▃▅▇▅▄▁▃█  17–25ms
📜 Previous results (1)

b4bb3ee

Thu, 24 Sep 2026 02:24:31 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 2161 (+55%) 🔻 2288 🔴 (+32%) 🔻 2351 🔴 (+33%) 🔻 2701 🔴 (+27%) 🔻 30
TTFS stream 288 (-81%) 💚 2232 🔴 (+34%) 🔻 2284 🔴 (+36%) 🔻 2366 🔴 (+39%) 🔻 30
TTFS hook + stream 2431 (+34%) 🔻 2575 🔴 (+27%) 🔻 2593 🔴 (+26%) 🔻 2710 🔴 (+28%) 🔻 30
Fan-out TTFS Promise.all(100 steps) 602 (+34%) 🔻 957 (+49%) 🔻 1115 (-45%) 💚 2631 (+26%) 🔻 10
Fan-out TTLS Promise.all(100 steps) 1692 (-20%) 💚 3341 (-13%) 3491 (-20%) 💚 10345 (+23%) 🔻 10
STSO 1020 steps (inline) 119 (+18%) 🔻 153 (+13%) 170 (+17%) 🔻 245 (-7.2%) 1019
WO 1020 steps 151351 (+12%) 151351 (+12%) 151351 (+12%) 151351 (+12%) 1
CRTT first chunk (pooled) 80 (+25%) 🔻 108 (+6.9%) 166 (+23%) 🔻 326 (-33%) 💚 28

Streams

Scenario CRTT 1st p75 p90 p99 CDV max iters
paced control (100/s, 60B) 104 (+19%) 177 (+14%) 245 (-18%) 704 (-26%) 169 (-34%) 10
size sweep (100/s, 160B-12KB) 91 (+22%) 156 (-7%) 220 (-28%) 364 (-40%) 163 (-24%) 10
replay gateway-gpt-5.4-nano-2000t (1x) 89 (-8%) 141 (+2%) 187 (-8%) 303 (-45%) 248 (-51%) 3
replay eve-gpt-5.6-sol-2000t (1x) 92 (+35%) 141 (-7%) 185 (-21%) 355 (+7%) 431 (+25%) 2
replay eve-gpt-5.6-sol-2000t (2x) 90 (-16%) 176 (-3%) 249 (+3%) 422 (-4%) 328 (+21%) 3
ℹ️ 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_41M38M3PKQ0GJJ87TN8MV9C2TW | 🔍 observability
  • promiseRaceWorkflow | wrun_41M38M3PKQ0GJJ87TN8MV9C2TX | 🔍 observability
  • promiseAnyWorkflow | wrun_41M38M3PKQ0GJJ87TN8MV9C2TY | 🔍 observability
  • retainedInterleavingWorkflow | wrun_41M38M42CT0GS2ARRWDJHSC8TH | 🔍 observability
  • hookWorkflow | wrun_41M38M43QX0GN1YA2A0QV9NQ6B | 🔍 observability
  • hookWorkflow is not resumable via public webhook endpoint | wrun_41M38M455E0GM8STQ2S9SP00ZY | 🔍 observability
  • sleepingWorkflow | wrun_41M38M4D5F0GTNDKWXY8WBJF5B | 🔍 observability
  • parallelSleepWorkflow | wrun_41M38M4DC40GQPS1G8M44R50ES | 🔍 observability
  • sleepWinsRaceWorkflow | wrun_41M38M4JSM0GMVD19VBKYEVQV3 | 🔍 observability
  • stepWinsRaceWorkflow | wrun_41M38M4KXN0GSVS0M420RPSK45 | 🔍 observability
  • nullByteWorkflow | wrun_41M38M4MEF0GS5QPSCGEAFZFXA | 🔍 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_41M38M5BER0GQV7BW715J0A3WA | 🔍 observability
  • utf8StreamWorkflow | wrun_41M38M5BFZ0GJR635V2DZ9YD7Y | 🔍 observability
  • writableForwardedFromWorkflowWorkflow | wrun_41M38M5CFR0GVWA0PT4HESH0WN | 🔍 observability
  • writableForwardedFromStepWorkflow | wrun_41M38M5DP60GJJ720YM9HP7DYQ | 🔍 observability
  • promiseRaceStressTestWorkflow | wrun_41M38M5GXJ0GT0GN35TKXSKDEW | 🔍 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_41M38M6YSB0GYBK7QY690RKGHA | 🔍 observability
  • concurrent hook token conflict - two workflows cannot use the same hook token simultaneously | wrun_41M38M6SA30GQ6X8E09BR3GXZ2 | 🔍 observability
  • hookGetConflictWorkflow - awaiting hook.getConflict() registers hook without payload | wrun_41M38M6TZ80GT9D20TEQNF57K5 | 🔍 observability
  • 'hookGetConflictWithPriorStepWorkflow' - hook.getConflict() does not block step execution | wrun_41M38M70G10GSJ9PVE65XCTC7C | 🔍 observability
  • 'hookGetConflictWithParallelStepWorkfl…' - hook.getConflict() does not block step execution | wrun_41M38M71E90GH0TAHAAZRSXAG6 | 🔍 observability
  • hookGetConflictThenStepParallelWorkflow - hook.getConflict() continuation step runs alongside other steps | wrun_41M38M72DC0GGGFN74AC6TCK43 | 🔍 observability
  • hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered | wrun_41M38M799E0GKYE2ZNCDPWRE91 | 🔍 observability
  • hookClaimOnlyMutexWorkflow - hook works as a pure run mutex without payload data | wrun_41M38M7VA90GKW1EG4B85Q085P | 🔍 observability
  • hookAdoptOwnerResultWorkflow - duplicate adopts the owner result via conflict.returnValue | wrun_41M38M7J950GWNKZ5YE4KKR00T | 🔍 observability
  • hookSignalOwnerWorkflow - duplicate forwards its payload to the owner via resumeHook | wrun_41M38M7M8D0GTVWRR5X5KQ87GM | 🔍 observability
  • resume-or-start route pattern - resumeHook retried after start() reaches the new run | wrun_41M38M87BK0GSGGCVFT591YEAM | 🔍 observability
  • hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running | wrun_41M38M8BFX0GS4KFYEWSPZQSFD | 🔍 observability
  • hookTokenReuseLoopWorkflow - same run recreates a hook with the same token after dispose() | wrun_41M38M8CGR0GGPEGNFHSZSDFW8 | 🔍 observability
  • spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step | wrun_41M38M8VJ50GMYH4JE00J3KKB3 | 🔍 observability
  • cancelRun - cancelling a running workflow | wrun_41M38M9P3M0GQKB0CP50EBCAF7 | 🔍 observability
  • cancelRun via CLI - cancelling a running workflow | wrun_41M38M9SXJ0GV9MS4RAV66DWP3 | 🔍 observability
  • hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep | wrun_41M38M9VJH0GX7KJZDXBRMWGE8 | 🔍 observability
  • hookWithSleepFinalStepWorkflow - step only on final payload | wrun_41M38M9XQZ0GS5MV0Z0TPATDD8 | 🔍 observability
  • sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration | wrun_41M38MA0HS0GXG2D2VFQM87JTK | 🔍 observability
  • sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) | wrun_41M38MA0SZ0GYHVRJZC8MNA5PJ | 🔍 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_41M38MAFCQ0GPA8WN2RAWXQ133 | 🔍 observability
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41M38MAG0T0GPM88XYWH7JQBKW | 🔍 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.

  • applies prepareCall before streaming (sveltekit · vercel-prod / vercel / node / production)
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context (nextjs-webpack · local-dev / local / node / stable)
  • fires constructor + stream callbacks in order with event data (nextjs-webpack · vercel-prod / vercel / node / production)
  • single tool call (nextjs-webpack · vercel-prod / vercel / node / production)
  • tool error recovery (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.

126 infra events
  • cold-start-warmup · suite warmup (python) · at 02:34:25Z · abandoned wrun_41M38M3KVZ0GXP736Z1H7M34C2 · (+7 more)
  • run-pickup-stall · promiseAnyWorkflow (python) · at 02:34:42Z · abandoned wrun_41M38M78T00GNRJ2X6P11V87Y1
  • run-pickup-stall · retainedInterleavingWorkflow (python) · at 02:34:42Z · abandoned wrun_41M38M78T00GNRJ2X6P11V87Y2
  • run-pickup-stall · promiseAllWorkflow (python) · at 02:34:42Z · abandoned wrun_41M38M78T00GNRJ2X6P11V87XZ
  • run-pickup-stall · hookWorkflow (python) · at 02:34:43Z · abandoned wrun_41M38M78T40GYV1MXJZJKCBT1E
  • run-pickup-stall · promiseRaceWorkflow (python) · at 02:34:43Z · abandoned wrun_41M38M78T00GNRJ2X6P11V87Y0
  • run-pickup-stall · retainedInterleavingWorkflow (python) · at 02:35:30Z · abandoned wrun_41M38M8QE20GYV8GGW96X0SA4Z
  • run-pickup-stall · hookWorkflow (python) · at 02:35:30Z · abandoned wrun_41M38M8R5F0GK5V8W4PEHQYQ3H
  • cold-start-warmup · suite warmup (tanstack-start) · at 02:35:43Z · abandoned wrun_01M38M8AGV1GX8K9CJPKW2Q6JC · (+1 more)
  • run-pickup-stall · promiseAnyWorkflow (python) · at 02:35:43Z · abandoned wrun_41M38M94EA0GYXQAJ20XQZZEHW
  • run-pickup-stall · promiseAllWorkflow (python) · at 02:35:43Z · abandoned wrun_41M38M94HK0GMK83035TX6X6E9
  • run-pickup-stall · promiseRaceWorkflow (python) · at 02:35:43Z · abandoned wrun_41M38M94G90GS1NHVNJET0T0CM
  • run-pickup-stall · hookWorkflow is not resumable via public webhook endpoint (python) · at 02:36:17Z · abandoned wrun_41M38MA5XE0GN8NJH7FWEWN5QG
  • run-pickup-stall · sleepingWorkflow (python) · at 02:36:18Z · abandoned wrun_41M38MA6Q80GKZYB2CR8CKXV37
  • run-pickup-stall · stepWinsRaceWorkflow (python) · at 02:36:46Z · abandoned wrun_41M38MB0XV0GKRK7RQVH2S3SEJ
  • run-pickup-stall · parallelSleepWorkflow (python) · at 02:36:46Z · abandoned wrun_41M38MB0FA0GMF2ABK91NK80C7
  • run-pickup-stall · sleepWinsRaceWorkflow (python) · at 02:36:46Z · abandoned wrun_41M38MB0PR0GWCMFME6DNZT5RY
  • run-pickup-stall · hookWorkflow is not resumable via public webhook endpoint (python) · at 02:38:14Z · abandoned wrun_41M38MCQB20GJC5GNZYDJEHJTJ
  • run-pickup-stall · sleepingWorkflow (python) · at 02:38:14Z · abandoned wrun_41M38MCQNX0GT0TBBQ4YDD43ZV
  • run-pickup-stall · parallelSleepWorkflow (python) · at 02:38:21Z · abandoned wrun_41M38MDRBT0GKR5JFZCENKWQN4
  • run-pickup-stall · sleepWinsRaceWorkflow (python) · at 02:38:21Z · abandoned wrun_41M38MDRKB0GVXARY5WEFDG9QG
  • run-pickup-stall · stepWinsRaceWorkflow (python) · at 02:38:21Z · abandoned wrun_41M38MDRTD0GWXAMHT2JWMZ90H
  • run-pickup-stall · nullByteWorkflow (python) · at 02:38:41Z · abandoned wrun_41M38MEJSG0GKMNRCKYNFS2HB0
  • run-pickup-stall · no startIndex (reads all chunks) (python) · at 02:38:42Z · abandoned wrun_41M38MEK2F0GXWFQ00S4QQAYDW
  • run-pickup-stall · positive startIndex (skips first chunk) (python) · at 02:39:15Z · abandoned wrun_41M38MFJYV0GG03NKT7466TXYM
  • run-pickup-stall · negative startIndex (reads from end) (python) · at 02:39:15Z · abandoned wrun_41M38MFK6B0GMQTQ602DJKXJVT
  • run-pickup-stall · getTailIndex returns correct index after stream completes (python) · at 02:39:17Z · abandoned wrun_41M38MFNXF0GTF90T9V73R3A20
  • run-pickup-stall · getTailIndex returns -1 before any chunks are written (python) · at 02:39:42Z · abandoned wrun_41M38MGDCG0GX3F03HN6ZXS5AT
  • run-pickup-stall · getChunks returns same content as reading the stream (python) · at 02:39:42Z · abandoned wrun_41M38MGDNG0GNE5D9G8ZNHD9X8
  • run-pickup-stall · outputStreamInsideStepWorkflow - getWritable() called inside step functions (python) · at 02:39:58Z · abandoned wrun_41M38MGXR60GVCKSGX5BWYXDQB
  • run-pickup-stall · utf8StreamWorkflow (python) · at 02:40:14Z · abandoned wrun_41M38MHDHV0GT1R14SEZEZSRED
  • run-pickup-stall · nullByteWorkflow (python) · at 02:40:17Z · abandoned wrun_41M38MHGNK0GJSQWESA3NTPFFE
  • run-pickup-stall · no startIndex (reads all chunks) (python) · at 02:40:18Z · abandoned wrun_41M38MHHBM0GQ67E81DFDP6BTZ
  • run-pickup-stall · stepFunctionAsStartArgWorkflow - step function reference passed as start() argument (nextjs-webpack) · at 02:40:33Z · abandoned wrun_01M38MJ0XPV4X6AYE98N6NWNAC
  • run-pickup-stall · cancelRun - cancelling a running workflow (nextjs-webpack) · at 02:40:34Z · abandoned wrun_01M38MJ208VA292M59CSCFQYT8
  • run-pickup-stall · positive startIndex (skips first chunk) (python) · at 02:40:43Z · abandoned wrun_41M38MJ9420GG873NGK8MHQ8MY
  • run-pickup-stall · negative startIndex (reads from end) (python) · at 02:40:58Z · abandoned wrun_41M38MJRB60GNCG2PDA0NM7XH9
  • run-pickup-stall · plainModuleDoneHook resumed via plain API route (o2flow shape) (nextjs-webpack) · at 02:41:01Z · abandoned wrun_01M38MJWHMTSTNCWNMQ3M5TCK0
  • run-pickup-stall · getTailIndex returns correct index after stream completes (python) · at 02:41:18Z · abandoned wrun_41M38MKBYK0GMXFQ9EEBEP7YVE
  • run-pickup-stall · getChunks returns same content as reading the stream (python) · at 02:41:21Z · abandoned wrun_41M38MKERD0GW4Y4ZAM7432V51
  • run-pickup-stall · outputStreamInsideStepWorkflow - getWritable() called inside step functions (python) · at 02:41:43Z · abandoned wrun_41M38MM3Q30GT88VXA68EF6H1M
  • run-pickup-stall · writableForwardedFromWorkflowWorkflow (python) · at 02:41:58Z · abandoned wrun_41M38MMJY50GJVCZQHC5KVNT2W
  • run-pickup-stall · writableForwardedFromStepWorkflow (python) · at 02:42:17Z · abandoned wrun_41M38MN55D0GJYTN8MHK52SY7T
  • run-pickup-stall · utf8StreamWorkflow (python) · at 02:42:18Z · abandoned wrun_41M38MN6HM0GMX84KC26Y3CMDF
  • run-pickup-stall · promiseRaceStressTestWorkflow (python) · at 02:42:23Z · abandoned wrun_41M38MNB4X0GHB8VCM9KS3BXD2
  • run-pickup-stall · regular Error retries until success (python) · at 02:42:44Z · abandoned wrun_41M38MNZ390GG3WDS99PYH3MDJ
  • run-pickup-stall · FatalError fails immediately without retries (python) · at 02:43:24Z · abandoned wrun_41M38MQ5QX0GNDYZCSR078CDTC
  • run-pickup-stall · RetryableError respects custom retryAfter delay (python) · at 02:43:43Z · abandoned wrun_41M38MQSP90GRSS0695P5BY17P
  • run-pickup-stall · maxRetries=0 disables retries (python) · at 02:43:58Z · abandoned wrun_41M38MR8460GP6N4AMXFMDTE98
  • run-pickup-stall · FatalError can be caught and detected with FatalError.is() (python) · at 02:44:17Z · abandoned wrun_41M38MRTBE0GK4705XF0YZA30P
  • run-pickup-stall · step throw round-trips FatalError with cause chain to workflow catch (python) · at 02:44:18Z · abandoned wrun_41M38MRVQM0GRMCVAEF1SVYP2K
  • run-pickup-stall · workflow throw round-trips FatalError + cause through run_failed event (python) · at 02:44:23Z · abandoned wrun_41M38MS0AX0GGZGX6RH55V5C0H
  • run-pickup-stall · StepNotRegisteredError fails the step but workflow can catch it (python) · at 02:44:44Z · abandoned wrun_41M38MSM990GP7JJE2TDCM96NP
  • run-pickup-stall · StepNotRegisteredError fails the run when not caught in workflow (python) · at 02:45:02Z · abandoned wrun_41M38MT2Q70GJ90V8MQFVHZX81
  • run-pickup-stall · promiseRaceStressTestWorkflow (python) · at 02:46:25Z · abandoned wrun_41M38MVVHW0GXW7Y1JEFCNWATV
  • run-pickup-stall · regular Error retries until success (python) · at 02:46:28Z · abandoned wrun_41M38MVXA70GKDP3A5SEGQW783
  • run-pickup-stall · writableForwardedFromWorkflowWorkflow (python) · at 02:46:32Z · abandoned wrun_41M38MVYTZ0GSQ5KGYXD7V646C
  • run-pickup-stall · writableForwardedFromStepWorkflow (python) · at 02:46:34Z · abandoned wrun_41M38MWB3B0GJNJYC2AHQAE601
  • run-pickup-stall · hookCleanupTestWorkflow - hook token reuse after workflow completion (python) · at 02:46:53Z · abandoned wrun_41M38MWESX0GTY9GTPDDHTZYSM
  • run-pickup-stall · FatalError fails immediately without retries (python) · at 02:47:35Z · abandoned wrun_41M38MXQX70GGKY111PWT5W931
  • run-pickup-stall · RetryableError respects custom retryAfter delay (python) · at 02:48:04Z · abandoned wrun_41M38MY9CW0GSY68K2KS6EQCHH
  • run-pickup-stall · maxRetries=0 disables retries (python) · at 02:48:05Z · abandoned wrun_41M38MYKED0GVH8XGMERGWTGKS
  • run-pickup-stall · step throw round-trips FatalError with cause chain to workflow catch (python) · at 02:48:18Z · abandoned wrun_41M38N009B0GPK7P8HQWRPRWVR
  • run-pickup-stall · FatalError can be caught and detected with FatalError.is() (python) · at 02:48:19Z · abandoned wrun_41M38MZM0Z0GXT97QWMMVRQWP3
  • run-pickup-stall · workflow throw round-trips FatalError + cause through run_failed event (python) · at 02:48:23Z · abandoned wrun_41M38N03ZV0GNZB193CBRK8F8Z
  • run-pickup-stall · StepNotRegisteredError fails the step but workflow can catch it (python) · at 02:48:32Z · abandoned wrun_41M38N0JVJ0GYNK29Y0QNTQQRD
  • run-pickup-stall · StepNotRegisteredError fails the run when not caught in workflow (python) · at 02:48:36Z · abandoned wrun_41M38N0QJ70GYK8SK2SCNFTN00
  • run-pickup-stall · hookCleanupTestWorkflow - hook token reuse after workflow completion (python) · at 02:49:12Z · abandoned wrun_41M38N1TWB0GKEPW06WRJA10PV
  • run-pickup-stall · hookGetConflictWorkflow - awaiting hook.getConflict() registers hook without payload (python) · at 02:49:16Z · abandoned wrun_41M38N1YJW0GN1EJ9RR8CGKZ3A
  • run-pickup-stall · 'hookGetConflictWithPriorStepWorkflow' - hook.getConflict() does not block step execution (python) · at 02:49:31Z · abandoned wrun_41M38N2DEJ0GXARVRKXJSBQTTF
  • run-pickup-stall · 'hookGetConflictWithParallelStepWorkfl…' - hook.getConflict() does not block step execution (python) · at 02:49:37Z · abandoned wrun_41M38N2JYF0GZ06BZBZ7TXSQ87
  • run-pickup-stall · hookGetConflictThenStepParallelWorkflow - hook.getConflict() continuation step runs alongside other steps (python) · at 02:50:00Z · abandoned wrun_41M38N39YT0GK0ZASJHCATJ8YQ
  • run-pickup-stall · hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered (python) · at 02:50:09Z · abandoned wrun_41M38N3JNB0GY0H036S6R02Z9M
  • run-pickup-stall · hookGetConflictWorkflow - awaiting hook.getConflict() registers hook without payload (python) · at 02:50:17Z · abandoned wrun_41M38N3T150GR452BK07G2XR7V
  • run-pickup-stall · 'hookGetConflictWithPriorStepWorkflow' - hook.getConflict() does not block step execution (python) · at 02:50:32Z · abandoned wrun_41M38N48XV0GYG7XHKF5G6BA3V
  • run-pickup-stall · 'hookGetConflictWithParallelStepWorkfl…' - hook.getConflict() does not block step execution (python) · at 02:50:38Z · abandoned wrun_41M38N4EG10GY0ZKNEPRAHME5A
  • run-pickup-stall · hookGetConflictWorkflow - hook.getConflict() resolves with the conflicting run when token is already registered (python) · at 02:50:57Z · abandoned wrun_41M38N513M0GJ711RJRVVSHQ1N
  • run-pickup-stall · hookClaimOnlyMutexWorkflow - hook works as a pure run mutex without payload data (python) · at 02:51:18Z · abandoned wrun_41M38N5NDR0GKV8NJT2PYWGFGD
  • run-pickup-stall · hookGetConflictThenStepParallelWorkflow - hook.getConflict() continuation step runs alongside other steps (python) · at 02:51:31Z · abandoned wrun_41M38N62KB0GM0QJKEPE9PZF53
  • run-pickup-stall · hookAdoptOwnerResultWorkflow - duplicate adopts the owner result via conflict.returnValue (python) · at 02:51:33Z · abandoned wrun_41M38N649H0GSX62820H24TV6S
  • run-pickup-stall · hookSignalOwnerWorkflow - duplicate forwards its payload to the owner via resumeHook (python) · at 02:51:39Z · abandoned wrun_41M38N69X00GX88JTFAM0GKNR4
  • run-pickup-stall · resume-or-start route pattern - resumeHook retried after start() reaches the new run (python) · at 02:51:45Z · abandoned wrun_41M38N6FS70GP30PA8A0NQFA68
  • run-pickup-stall · hookClaimOnlyMutexWorkflow - hook works as a pure run mutex without payload data (python) · at 02:52:05Z · abandoned wrun_41M38N73H50GXHXJ5FBNEVWCRX
  • run-pickup-stall · hookAdoptOwnerResultWorkflow - duplicate adopts the owner result via conflict.returnValue (python) · at 02:52:20Z · abandoned wrun_41M38N7JCF0GG2X9X91SE2H18Z
  • run-pickup-stall · hookSignalOwnerWorkflow - duplicate forwards its payload to the owner via resumeHook (python) · at 02:52:26Z · abandoned wrun_41M38N7R2P0GN8NGQY6K68X2VE
  • run-pickup-stall · resume-or-start route pattern - resumeHook retried after start() reaches the new run (python) · at 02:52:32Z · abandoned wrun_41M38N7Y630GZYQSYWMFG22DM3
  • run-pickup-stall · hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running (python) · at 02:52:53Z · abandoned wrun_41M38N8HSB0GSGCY5A5ACSD117
  • run-pickup-stall · hookTokenReuseLoopWorkflow - same run recreates a hook with the same token after dispose() (python) · at 02:53:02Z · abandoned wrun_41M38N8VAZ0GPFEAD8SB44SFX4
  • run-pickup-stall · spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step (python) · at 02:53:08Z · abandoned wrun_41M38N90ND0GGBQC4KJSXD61RQ
  • run-pickup-stall · cancelRun - cancelling a running workflow (python) · at 02:53:19Z · abandoned wrun_41M38N9AMV0GXG589D9FPN50MR
  • run-pickup-stall · cancelRun via CLI - cancelling a running workflow (python) · at 02:53:20Z · abandoned wrun_41M38N9CJY0GRM8DMT8WXP5WCT
  • run-pickup-stall · hookDisposeTestWorkflow - hook token reuse after explicit disposal while workflow still running (python) · at 02:53:41Z · abandoned wrun_41M38NA0AC0GQ6P44MJ8CNFH4G
  • run-pickup-stall · hookTokenReuseLoopWorkflow - same run recreates a hook with the same token after dispose() (python) · at 02:53:50Z · abandoned wrun_41M38NA9PQ0GSFEGAX1MCH79BJ
  • run-pickup-stall · cancelRun - cancelling a running workflow (python) · at 02:53:51Z · abandoned wrun_41M38NAB460GREX6Z43KNCVRBT
  • run-pickup-stall · cancelRun via CLI - cancelling a running workflow (python) · at 02:53:53Z · abandoned wrun_41M38NACKY0GVHYWCF6CTA8MEZ
  • run-pickup-stall · hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep (python) · at 02:54:23Z · abandoned wrun_41M38NBARV0GH28KM3JHDF3WHS
  • run-pickup-stall · hookWithSleepFinalStepWorkflow - step only on final payload (python) · at 02:54:25Z · abandoned wrun_41M38NBC9R0GMMF6PG8KT7TDDB
  • run-pickup-stall · sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration (python) · at 02:54:29Z · abandoned wrun_41M38NBGC50GRE9PDP8PRDW1F2
  • run-pickup-stall · sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) (python) · at 02:54:37Z · abandoned wrun_41M38NBQZR0GYVV0SR8QKEH68D
  • run-pickup-stall · spawnWorkflowFromStepWorkflow - spawning a child workflow using start() inside a step (python) · at 02:55:10Z · abandoned wrun_41M38NCPP20GSFH90G0YW5K1SR
  • run-pickup-stall · hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep (python) · at 02:55:11Z · abandoned wrun_41M38NCS700GZFAFX08D0QTZC8
  • run-pickup-stall · hookWithSleepFinalStepWorkflow - step only on final payload (python) · at 02:55:13Z · abandoned wrun_41M38NCTRX0GTWF05DM6X58KZK
  • run-pickup-stall · sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration (python) · at 02:55:30Z · abandoned wrun_41M38NDBX50GXV14HAATKES37B
  • run-pickup-stall · sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) (python) · at 02:55:39Z · abandoned wrun_41M38NDKB80GHKSDP5Y9KQ1BX7
  • run-pickup-stall · abortTimeoutWorkflow: timeout cancels long-running step (python) · at 02:56:13Z · abandoned wrun_41M38NEDAM0GJ35RT11A3WWDYD
  • run-pickup-stall · abortParallelWorkflow: abort cancels all parallel steps (python) · at 02:56:16Z · abandoned wrun_41M38NEG770GPB7QVKQG0J94TX
  • run-pickup-stall · abortReasonWorkflow: abort reason preserved across boundaries (python) · at 02:57:03Z · abandoned wrun_41M38NF6G50GR9E4AEP2B74T56
  • run-pickup-stall · metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (Vercel World Regression: getWorkflowMetadata() fully breaking runs #1577) (python) · at 02:57:06Z · abandoned wrun_41M38NFDY70GGKYAHAQ2VC5X7P
  • run-pickup-stall · abortViaHookWorkflow: external hook triggers abort on in-flight step (python) · at 02:57:58Z · abandoned wrun_41M38NGAT70GNNWXKY2M9TWRRX
  • run-pickup-stall · abortParallelWorkflow: abort cancels all parallel steps (python) · at 02:58:17Z · abandoned wrun_41M38NJ5D80GWZW2GBKABHK68F
  • run-pickup-stall · abortTimeoutWorkflow: timeout cancels long-running step (python) · at 02:58:18Z · abandoned wrun_41M38NHYCN0GZ3333ZJGQ84FD2
  • run-pickup-stall · start: initial attributes are seeded on run creation (python) · at 02:58:27Z · abandoned wrun_41M38NJMR90GKWP9CNQS00FKSS
  • run-pickup-stall · start: reserved-prefix initial attributes are seeded with allowReservedAttributes (python) · at 02:58:31Z · abandoned wrun_41M38NJTZN0GTY03BCF29HDHPV
  • run-pickup-stall · setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly (python) · at 02:58:38Z · abandoned wrun_41M38NK3470GZ50NDXZAAH8H71
  • run-pickup-stall · setAttributesInsideStepWorkflow: step-body calls append attributed native events (python) · at 02:58:53Z · abandoned wrun_41M38NKJ1S0GN327RARZXJZJ6X
  • run-pickup-stall · metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (Vercel World Regression: getWorkflowMetadata() fully breaking runs #1577) (python) · at 02:58:59Z · abandoned wrun_41M38NKR940GMYMJRB5CS4BF27
  • run-pickup-stall · abortReasonWorkflow: abort reason preserved across boundaries (python) · at 02:59:00Z · abandoned wrun_41M38NKRZQ0GS37W0XP81D82MT
  • run-pickup-stall · abortViaHookWorkflow: external hook triggers abort on in-flight step (python) · at 02:59:07Z · abandoned wrun_41M38NM0070GNGHHWN27AX1D52
  • run-pickup-stall · Promise.all of disjoint-key writes: every key lands (python) · at 02:59:10Z · abandoned wrun_41M38NM2AR0GQ32QCMJ5T6D4EE
  • run-pickup-stall · start: initial attributes are seeded on run creation (python) · at 02:59:37Z · abandoned wrun_41M38NMX110GPD6368JK5PZH8E
  • run-pickup-stall · start: reserved-prefix initial attributes are seeded with allowReservedAttributes (python) · at 02:59:40Z · abandoned wrun_41M38NMZM80GGCHG49EFQ1C2J1
  • run-pickup-stall · setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly (python) · at 02:59:55Z · abandoned wrun_41M38NNEH00GSBZJ510HP0C5TH
  • run-pickup-stall · setAttributesInsideStepWorkflow: step-body calls append attributed native events (python) · at 03:00:01Z · abandoned wrun_41M38NNKWY0GTV96WAVTNCK5KB
  • run-pickup-stall · Promise.all of disjoint-key writes: every key lands (python) · at 03:00:03Z · abandoned wrun_41M38NNNY40GTSMREFQ8WDZN5Y
  • run-pickup-stall · workflow throws after awaited setAttributes: attribute still persists on the failed run (python) · at 03:00:08Z · abandoned wrun_41M38NNV330GVQEQ4RW4PD9GTV
  • run-pickup-stall · workflow throws after awaited setAttributes: attribute still persists on the failed run (python) · at 03:00:39Z · abandoned wrun_41M38NPS640GV56V53X8S6G2TC

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

Sim World

Simulated world deterministic testing for races. Traces

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

fence=per-spec

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

Full trace: world-sim.txt

@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor
Framework Flow route Step reg. Framework output
hono 252.0 KiB (±0) 94.9 KiB (±0) 1.91 MiB (+26 B)
nextjs-turbopack 259.3 KiB (±0) 426 B (±0) 907.5 KiB (-2 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.

2e457c6 · run

@pranaygp pranaygp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Narrow, correct fix for #3909, and it matches world-postgres (#4114). Both transports treat 0 as "no deadline" (undici natively; nodeHttpFetch via its if (headersTimeoutMs) / if (!bodyTimeoutMs) guards), and close() still aborts in-flight deliveries.

Verified

  • pnpm --filter @workflow/world-local test: 598 passed, 4 skipped.
  • End-to-end repro with a real createQueue and a handler that takes 35s, on both undici and WORKFLOW_NODE_HTTP=1:
    • New default: 1 delivery, for a stall before headers and for a stall mid-body.
    • Opt-in 30000 (the old default): 2 deliveries in every case, i.e. #3909 reproduces.
  • Node's http.Server.requestTimeout does not cut off a slow response (requestTimeout=1000 with a 4s handler gave 1 delivery), so there's no hidden server-side deadline that would bring the redelivery back.

Follow-ups (not blocking)

  1. Pre-existing, now more likely: an opt-in value above 2³¹−1 ms breaks every delivery on the node:http transport. The new docs tell operators to set the deadline "above the longest inline work you expect", so some will pick a huge value to be safe. envTimeoutMs (queue.ts:82) doesn't clamp, and Node's setTimeout treats an overflowing delay as 1ms (TimeoutOverflowWarning). With WORKFLOW_NODE_HTTP=1 WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS=3000000000 and a 3s handler I got 2 no response headers within 3000000000ms failures within about 5s. Every retry dies after 1ms until the 256-attempt safety limit. undici is fine. Fix: clamp to MAX_SAFE_TIMEOUT_MS. world-postgres has the same helper (world-postgres/src/queue.ts:114).
  2. Pre-existing, now more likely: parseInt makes plausible typos into tiny deadlines. 1e4 parses as 1ms, 30s as 30ms, 30_000 as 30ms. That now triggers exactly the duplicate step execution the docs warn about. Suggest strict parsing (Number(raw) plus Number.isSafeInteger) and a warning when an override is ignored.
  3. Observability: a hung handler is now silent. It holds its semaphore slot forever with no log line. A non-destructive watchdog (a warn or debugLog after N minutes naming runId/stepId/messageId, with no abort) would make stuck runs debuggable. Related: the transport-retry log uses String(err), which on undici prints fetch failed and drops the HeadersTimeoutError/BodyTimeoutError cause. Logging the cause and naming the env var would help opt-in users.
  4. Test: consider asserting that the Agent (or the nodeHttpFetch per-request options) is actually constructed with 0/0 when no env is set, so the test covers the wiring and not only getQueueAgentOptions().

Nits

  • docs/content/docs/v5/configuration/worlds.mdx: the WORKFLOW_LOCAL_BODY_TIMEOUT_MS entry didn't get the re-execution caution that the headers entry and local.mdx have. Also, neither page now says explicitly that 0 disables the deadline, which helps someone switching back from an opt-in value.
  • queue.test.ts:561: the first assertion compares against the exported constants, so it can't fail when a constant changes. Asserting { headersTimeout: 0, bodyTimeout: 0 } directly reads as the spec.
  • queue.ts:79: the DEFAULT_BODY_TIMEOUT_MS doc comment doesn't say "none".

Copy link
Copy Markdown
Contributor

@VaguelySerious here are the regression tests I'd recommend, written and run locally against this branch (b4bb3ee9).

1. For this PR: pin the wiring, not just getQueueAgentOptions()

The current default test would still pass if createQueue stopped using getQueueAgentOptions(). This file records what each transport is actually built with. It passes on this branch, and both tests fail if DEFAULT_*_TIMEOUT_MS is set back to 30_000 (I checked). It lives in its own file because it vi.mocks undici and @workflow/world/node-http.js.

packages/world-local/src/queue-deadline-defaults.test.ts
import { createServer, type Server } from 'node:http';
import type { AddressInfo } from 'node:net';
import { NODE_HTTP_ENV_VAR } from '@workflow/world';
import { afterEach, describe, expect, it, vi } from 'vitest';

// Record what each transport is actually built with, so the test pins the
// wiring (#3909) rather than only the value `getQueueAgentOptions()` returns.
const agentOptions = vi.hoisted(() => [] as Record<string, unknown>[]);
const fetchOptions = vi.hoisted(() => [] as Record<string, unknown>[]);

vi.mock('undici', async (importOriginal) => {
  const actual = await importOriginal<typeof import('undici')>();
  class RecordingAgent extends actual.Agent {
    constructor(options: ConstructorParameters<typeof actual.Agent>[0]) {
      agentOptions.push({ ...options });
      super(options);
    }
  }
  return { ...actual, Agent: RecordingAgent };
});

vi.mock('@workflow/world/node-http.js', async (importOriginal) => {
  const actual =
    await importOriginal<typeof import('@workflow/world/node-http.js')>();
  return {
    ...actual,
    nodeHttpFetch: (url: string, init: Record<string, unknown>) => {
      fetchOptions.push({
        headersTimeoutMs: init.headersTimeoutMs,
        bodyTimeoutMs: init.bodyTimeoutMs,
      });
      return actual.nodeHttpFetch(url, init as never);
    },
  };
});

const { createQueue } = await import('./queue.js');

const payload = { runId: 'run_01ABC', stepId: 'step_01ABC', stepName: 's' };

describe('queue delivery deadlines are off by default (#3909)', () => {
  let server: Server | undefined;

  afterEach(async () => {
    agentOptions.length = 0;
    fetchOptions.length = 0;
    vi.unstubAllEnvs();
    if (server) {
      server.closeAllConnections();
      await new Promise((resolve) => server?.close(resolve));
      server = undefined;
    }
  });

  it('builds the undici Agent with no headers/body deadline', async () => {
    vi.stubEnv(NODE_HTTP_ENV_VAR, '0');
    const queue = createQueue({ baseUrl: 'http://127.0.0.1:1' });
    await queue.close();
    expect(agentOptions).toHaveLength(1);
    expect(agentOptions[0]).toMatchObject({
      headersTimeout: 0,
      bodyTimeout: 0,
    });
  });

  it('passes no headers/body deadline to node:http deliveries', async () => {
    vi.stubEnv(NODE_HTTP_ENV_VAR, '1');
    server = createServer((request, response) => {
      request.resume();
      response.setHeader('content-type', 'application/json');
      response.end(JSON.stringify({ ok: true }));
    });
    await new Promise<void>((resolve) => {
      server?.listen(0, '127.0.0.1', resolve);
    });
    const { port } = server.address() as AddressInfo;
    const queue = createQueue({ baseUrl: `http://127.0.0.1:${port}` });
    try {
      await queue.queue('__wkf_workflow_test' as any, payload);
      await vi.waitFor(() => expect(fetchOptions).toHaveLength(1));
      expect(fetchOptions[0]).toEqual({ headersTimeoutMs: 0, bodyTimeoutMs: 0 });
      expect(agentOptions).toHaveLength(0);
    } finally {
      await queue.close();
    }
  });
});

Small tightening of the existing default test (queue.test.ts:561): assert the spec directly rather than comparing against the exported constants.

expect(getQueueAgentOptions()).toMatchObject({ headersTimeout: 0, bodyTimeout: 0 });

2. For the envTimeoutMs follow-up (this PR or the next)

These cover the two pre-existing issues from my review that this PR makes more likely. They fail on the current code: 3000000000 passes through unclamped, and 30s becomes 30.

Add them to the queue transport timeouts describe in queue.test.ts:

it('clamps an opt-in deadline to the largest delay setTimeout honors', () => {
  // Node treats a delay above 2^31-1 as 1ms, so over node:http an unclamped
  // "very large" deadline fails every delivery immediately.
  process.env.WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS = '3000000000';
  process.env.WORKFLOW_LOCAL_BODY_TIMEOUT_MS = '9999999999999';
  expect(getQueueAgentOptions()).toMatchObject({
    headersTimeout: 2_147_483_647,
    bodyTimeout: 2_147_483_647,
  });
});

it.each(['30s', '30_000', '1.5'])(
  'ignores a malformed override %j instead of truncating it',
  (raw) => {
    process.env.WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS = raw;
    expect(getQueueAgentOptions().headersTimeout).toBe(DEFAULT_HEADERS_TIMEOUT_MS);
  }
);

it('reads an exponent override as its full value', () => {
  process.env.WORKFLOW_LOCAL_HEADERS_TIMEOUT_MS = '1e4'; // parseInt gives 1
  expect(getQueueAgentOptions().headersTimeout).toBe(10_000);
});

A fix that makes them pass, with the rest of queue.test.ts still green (verified locally):

function envTimeoutMs(name: string, fallback: number): number {
  const raw = process.env[name];
  if (raw === undefined || raw === '') return fallback;
  const parsed = Number(raw.trim());
  if (!Number.isSafeInteger(parsed) || parsed < 0) return fallback;
  // Node treats a delay above 2^31-1 as 1ms, which would fail every
  // node:http delivery immediately instead of never.
  return Math.min(parsed, MAX_SAFE_TIMEOUT_MS);
}

world-postgres/src/queue.ts:114 has the identical helper and should get the same fix and tests (WORKFLOW_POSTGRES_*_TIMEOUT_MS).

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

looks good

Records the options the undici Agent and node:http deliveries are built
with, so the test fails if createQueue stops honoring the defaults.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@VaguelySerious
VaguelySerious enabled auto-merge (squash) September 24, 2026 02:38
@VaguelySerious
VaguelySerious merged commit 12fcca0 into main Sep 24, 2026
182 of 185 checks passed
@VaguelySerious
VaguelySerious deleted the peter/world-local-no-default-deadline branch September 24, 2026 17:19
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for 12fcca0 (AI decision).

This is a genuine correctness fix (duplicate step execution from a 30s queue delivery deadline), but the defect does not exist on stable: origin/stable:packages/world-local/src/queue.ts still hardcodes headersTimeout: 0 with undici's 300s bodyTimeout and has no DEFAULT_HEADERS_TIMEOUT_MS/DEFAULT_BODY_TIMEOUT_MS constants or WORKFLOW_LOCAL_*_TIMEOUT_MS overrides, so the regression from #3255 is main-only. The touched docs (docs/content/docs/v5/configuration/worlds.mdx, docs/content/worlds/v5/local.mdx) are also absent from stable, confirming the change targets main-only code.

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

12fcca0bcd187269654bcb921a1aa63d91e71ac9

This branch was successfully deployed

18 active deployments
Preview – workflow-swc-playground — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – workflow-docs — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – workbench-express-workflow — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – workbench-sveltekit-workflow — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – example-nextjs-workflow-turbopack — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – example-nextjs-workflow-webpack — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – example-workflow — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – workbench-fastify-workflow — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – workbench-nuxt-workflow — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – workbench-hono-workflow — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – workbench-nitro-workflow — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – workbench-tanstack-start-workflow — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – workbench-vite-workflow — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – workflow-tarballs — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – workbench-astro-workflow — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – workflow-web — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – workbench-nestjs-workflow — 2e457c6b Deployed Sep 24, 2026 by vercel[bot]
Preview – workbench-python-workflow — 2e457c6b 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.

Local world: 30s undici transport timeout redelivers a live queue message and re-executes an in-flight lazy-inline step

3 participants