Skip to content

Add world.snapshots storage interface (local, postgres, vercel) - #3250

Open
TooTallNate wants to merge 3 commits into
quickjs-vm-perffrom
quickjs-vm-snapshots
Open

Add world.snapshots storage interface (local, postgres, vercel)#3250
TooTallNate wants to merge 3 commits into
quickjs-vm-perffrom
quickjs-vm-snapshots

Conversation

@TooTallNate

Copy link
Copy Markdown
Member

Note

Supersedes #3050, which GitHub auto-marked as merged (and auto-deleted the head branch) when a restacking mistake briefly force-pushed the head branch to the same commit as its base. Same content, freshly rebased on the stack.

Stacked PR — based on #3049 (quickjs-vm-perf), which is based on #3048 (quickjs-vm). Review only the top commit here until the bases merge.

Summary

PR 3 of the QuickJS VM roadmap: the world.snapshots storage interface and its implementations, ported from the original snapshot-runtime branch (#1300). Inert until PR 4 — nothing in the runtime calls these APIs yet.

What's included

  • @workflow/world: Storage.snapshots interface — save(runId, data, metadata) / load(runId) / delete(runId) — plus SnapshotMetadata (eventsCursor, createdAt). The cursor is storage-layer metadata stored alongside the opaque snapshot bytes so a restore only fetches events recorded after the snapshot.
  • @workflow/world-local: filesystem storage under {basedir}/snapshots/ ({runId}.bin + {runId}.json sidecar), with round-trip unit tests.
  • @workflow/world-postgres: workflow_snapshots table (migration 0016, renumbered from the original branch to follow main's 00120015).
  • @workflow/world-vercel: client for the workflow-server snapshot endpoints (PUT/GET/DELETE /v2/runs/:runId/snapshot, binary body + metadata headers — endpoints are live in workflow-server since vercel/workflow-server#339). Includes:
    • the undici.request() (not fetch()) PUT path so Buffer bodies survive RetryAgent retries (Retry Handler Fails When Request Has a Body nodejs/undici#3288),
    • explicit W3C trace-context injection on all three request paths (they route around makeRequest, per the repo's trace-propagation rule), covered by new trace-propagation.test.ts cases.

Notes

  • Bytes are transported opaquely: compression/encryption is @workflow/core's responsibility (PR 4's save pipeline); worlds must not add their own compression (ciphertext doesn't compress).
  • Adding a required member to Storage is a breaking change for community World implementations — appropriate for the v5 beta line; called out in the changeset.
  • The always-on WORLD_SNAPSHOT_DIAG warn logging from the original branch was demoted to console.debug.

Copilot AI review requested due to automatic review settings July 31, 2026 03:21
@TooTallNate
TooTallNate requested review from a team and ijjk as code owners July 31, 2026 03:21
@changeset-bot

changeset-bot Bot commented Jul 31, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d2a115b

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

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

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

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

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.

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

Some tests failed

❌ Failed E2E Tests

▲ Vercel Production (372 failed)

astro-node (12 failed):

  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [hook-first-hook-first]: hook.then → addEventListener → resumeHook → abort()
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_41KZ53C06S0GMQGQHBNW4A957P | 🔍 observability
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36 | 🔍 observability
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes Promise.all of disjoint-key writes: every key lands
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

astro-quickjs (16 failed):

  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [hook-first-hook-first]: hook.then → addEventListener → resumeHook → abort()
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_41KZ53C06S0GMQGQHBNW4A957P | 🔍 observability
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_41KZ53C2PT0GW3VJA24MSZSXCW | 🔍 observability
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36 | 🔍 observability
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T | 🔍 observability
  • distributedAbortController - TTL expiration triggers signal | wrun_41KZ53CGYY0GPFXKPCA7PA0AY7 | 🔍 observability
  • distributedAbortController - reconnect to existing controller | wrun_41KZ53CR8G0GN5R7BBVBNRX1BM | 🔍 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 fire-and-forget: void setAttributes lands without awaiting
  • setAttributes Promise.all of disjoint-key writes: every key lands
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

example-node (15 failed):

  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [hook-first-hook-first]: hook.then → addEventListener → resumeHook → abort()
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_41KZ53C06S0GMQGQHBNW4A957P | 🔍 observability
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41KZ53C5630GSM6FF8WM7TFN15 | 🔍 observability
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36 | 🔍 observability
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T | 🔍 observability
  • distributedAbortController - TTL expiration triggers signal | wrun_41KZ53CGYY0GPFXKPCA7PA0AY7 | 🔍 observability
  • distributedAbortController - reconnect to existing controller | wrun_41KZ53CR8G0GN5R7BBVBNRX1BM | 🔍 observability
  • 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
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

example-quickjs (12 failed):

  • AbortController abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step
  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [hook-first-hook-first]: hook.then → addEventListener → resumeHook → abort()
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_41KZ53C06S0GMQGQHBNW4A957P | 🔍 observability
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41KZ53C5630GSM6FF8WM7TFN15 | 🔍 observability
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36 | 🔍 observability
  • distributedAbortController - reconnect to existing controller | wrun_41KZ53CR8G0GN5R7BBVBNRX1BM | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events

express-node (11 failed):

  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_41KZ53C2PT0GW3VJA24MSZSXCW | 🔍 observability
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41KZ53C5630GSM6FF8WM7TFN15 | 🔍 observability
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36 | 🔍 observability
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T | 🔍 observability
  • distributedAbortController - TTL expiration triggers signal | wrun_41KZ53CGYY0GPFXKPCA7PA0AY7 | 🔍 observability
  • distributedAbortController - reconnect to existing controller | wrun_41KZ53CR8G0GN5R7BBVBNRX1BM | 🔍 observability
  • 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 fire-and-forget: void setAttributes lands without awaiting
  • setAttributes Promise.all of disjoint-key writes: every key lands

express-quickjs (22 failed):

  • sleepWinsRaceWorkflow | wrun_41KZ52PWD70GKYC7E26CPE07Z5 | 🔍 observability
  • AbortController abortAnyInStepWorkflow: AbortSignal.any inside a step composes deserialized signals
  • AbortController abortSurvivesReplayWorkflow: controller state consistent across replay
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries
  • AbortController abortReasonTypesWorkflow: various abort reason types propagate correctly
  • AbortController abortFetchInFlightWorkflow: aborting cancels an in-flight fetch
  • AbortController abortListenerWorkflow: signal.addEventListener fires on the deserialized step signal
  • AbortController abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step
  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_41KZ53C06S0GMQGQHBNW4A957P | 🔍 observability
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41KZ53C5630GSM6FF8WM7TFN15 | 🔍 observability
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36 | 🔍 observability
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T | 🔍 observability
  • distributedAbortController - TTL expiration triggers signal | wrun_41KZ53CGYY0GPFXKPCA7PA0AY7 | 🔍 observability
  • distributedAbortController - reconnect to existing controller | wrun_41KZ53CR8G0GN5R7BBVBNRX1BM | 🔍 observability
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

fastify-node (10 failed):

  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_41KZ53C2PT0GW3VJA24MSZSXCW | 🔍 observability
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41KZ53C5630GSM6FF8WM7TFN15 | 🔍 observability
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T | 🔍 observability
  • distributedAbortController - TTL expiration triggers signal | wrun_41KZ53CGYY0GPFXKPCA7PA0AY7 | 🔍 observability
  • distributedAbortController - reconnect to existing controller | wrun_41KZ53CR8G0GN5R7BBVBNRX1BM | 🔍 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 Promise.all of disjoint-key writes: every key lands
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

fastify-quickjs (17 failed):

  • AbortController abortAnyInStepWorkflow: AbortSignal.any inside a step composes deserialized signals
  • AbortController abortSurvivesReplayWorkflow: controller state consistent across replay
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries
  • AbortController abortReasonTypesWorkflow: various abort reason types propagate correctly
  • AbortController abortFetchUncaughtWorkflow: uncaught fetch AbortError is FatalError, no retries
  • AbortController abortDeterministicBranchWorkflow: if-check takes same path on first-run and replay
  • AbortController abortListenerWorkflow: signal.addEventListener fires on the deserialized step signal
  • AbortController abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [hook-first-hook-first]: hook.then → addEventListener → resumeHook → abort()
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_41KZ53C2PT0GW3VJA24MSZSXCW | 🔍 observability
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41KZ53C5630GSM6FF8WM7TFN15 | 🔍 observability
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36 | 🔍 observability
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T | 🔍 observability
  • setAttributes Promise.all of disjoint-key writes: every key lands
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

hono-node (12 failed):

  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_41KZ53C2PT0GW3VJA24MSZSXCW | 🔍 observability
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41KZ53C5630GSM6FF8WM7TFN15 | 🔍 observability
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36 | 🔍 observability
  • distributedAbortController - reconnect to existing controller | wrun_41KZ53CR8G0GN5R7BBVBNRX1BM | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes Promise.all of disjoint-key writes: every key lands
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

hono-quickjs (18 failed):

  • AbortController abortSurvivesReplayWorkflow: controller state consistent across replay
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries
  • AbortController abortReasonTypesWorkflow: various abort reason types propagate correctly
  • AbortController abortFetchUncaughtWorkflow: uncaught fetch AbortError is FatalError, no retries
  • AbortController abortFetchInFlightWorkflow: aborting cancels an in-flight fetch
  • AbortController abortDeterministicBranchWorkflow: if-check takes same path on first-run and replay
  • AbortController abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires
  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [hook-first-hook-first]: hook.then → addEventListener → resumeHook → abort()
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41KZ53C5630GSM6FF8WM7TFN15 | 🔍 observability
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36 | 🔍 observability
  • distributedAbortController - TTL expiration triggers signal | wrun_41KZ53CGYY0GPFXKPCA7PA0AY7 | 🔍 observability
  • distributedAbortController - reconnect to existing controller | wrun_41KZ53CR8G0GN5R7BBVBNRX1BM | 🔍 observability
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

nextjs-turbopack-node (20 failed):

  • pages router promiseAllWorkflow via pages router
  • plainModuleDoneHook resumed via plain API route (o2flow shape) | wrun_41KZ538SKN0GJ7PQXKM7YPPE6D | 🔍 observability
  • hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep | wrun_41KZ536MRD0GXQ50NTR415FR70 | 🔍 observability
  • hookWithSleepFinalStepWorkflow - step only on final payload | wrun_41KZ536Z1K0GWK01YQV3Z3H9SB | 🔍 observability
  • sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration | wrun_41KZ5376YA0GVQAAGRMF0YXS7R | 🔍 observability
  • sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) | wrun_41KZ537GMH0GY2MYSA6W6K3T29 | 🔍 observability
  • AbortController abortTimeoutWorkflow: timeout cancels long-running step
  • AbortController abortParallelWorkflow: abort cancels all parallel steps
  • AbortController abortFromStepWorkflow: step abort cancels an in-flight sibling step
  • AbortController abortAlreadyAbortedWorkflow: pre-aborted signal seen by step
  • AbortController abortReasonWorkflow: abort reason preserved across boundaries
  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortExternalSignalInFlightWorkflow: external abort fires mid-flight, propagates to nested steps
  • AbortController abortAnyInStepWorkflow: AbortSignal.any inside a step composes deserialized signals
  • AbortController abortFetchUncaughtWorkflow: uncaught fetch AbortError is FatalError, no retries
  • AbortController abortFetchInFlightWorkflow: aborting cancels an in-flight fetch
  • AbortController abortVoidSleepTimeoutWorkflow: documented void sleep().then(abort) pattern works
  • AbortController abortDeterministicBranchWorkflow: if-check takes same path on first-run and replay
  • AbortController abortListenerWorkflow: signal.addEventListener fires on the deserialized step signal
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly

nextjs-turbopack-quickjs (18 failed):

  • pathsAliasWorkflow - TypeScript path aliases resolve correctly | wrun_41KZ5344ZV0GPVKH48C68FDP07 | 🔍 observability
  • Calculator.calculate - static workflow method using static step methods from another class | wrun_41KZ534BJ80GVQW424G164E1YX | 🔍 observability
  • AllInOneService.processNumber - static workflow method using sibling static step methods | wrun_41KZ534J120GQQB9S49GDNQM9Q | 🔍 observability
  • ChainableService.processWithThis - static step methods using this to reference the class | wrun_41KZ534RCX0GWXTX8CK100WZ6P | 🔍 observability
  • thisSerializationWorkflow - step function invoked with .call() and .apply() | wrun_41KZ534YTA0GN65D567XCFFSK4 | 🔍 observability
  • customSerializationWorkflow - custom class serialization with WORKFLOW_SERIALIZE/WORKFLOW_DESERIALIZE | wrun_41KZ5355C10GS8A6J9BY4ZKCKF | 🔍 observability
  • instanceMethodStepWorkflow - instance methods with "use step" directive | wrun_41KZ535BQF0GN8EV6VQD374PK5 | 🔍 observability
  • crossContextSerdeWorkflow - classes defined in step code are deserializable in workflow context | wrun_41KZ535QG50GXHH6R0QH8B8GNB | 🔍 observability
  • errorSubclassRoundTripWorkflow - first-class Error subclasses survive every serialization boundary | wrun_41KZ535Y2G0GQJHMD6K8NJSNYG | 🔍 observability
  • stepFunctionAsStartArgWorkflow - step function reference passed as start() argument | wrun_41KZ5360DF0GVW55B3642QK41X | 🔍 observability
  • cancelRun - cancelling a running workflow | wrun_41KZ5366QR0GGGVCMQ2T3WPEC6 | 🔍 observability
  • cancelRun via CLI - cancelling a running workflow | wrun_41KZ536C6P0GKAF3F8V82GETG4 | 🔍 observability
  • pages router addTenWorkflow via pages router
  • pages router sleepingWorkflow via pages router
  • hookWithSleepWorkflow - hook payloads delivered correctly with concurrent sleep | wrun_41KZ536MRD0GXQ50NTR415FR70 | 🔍 observability
  • AbortController abortFromStepWorkflow: step abort cancels an in-flight sibling step
  • AbortController abortAlreadyAbortedWorkflow: pre-aborted signal seen by step
  • AbortController abortReasonWorkflow: abort reason preserved across boundaries

nextjs-webpack-node (25 failed):

  • AbortController abortReasonWorkflow: abort reason preserved across boundaries
  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortExternalSignalWorkflow: signal passed as workflow input
  • AbortController abortExternalSignalInFlightWorkflow: external abort fires mid-flight, propagates to nested steps
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM
  • AbortController abortAnyInStepWorkflow: AbortSignal.any inside a step composes deserialized signals
  • AbortController abortSurvivesReplayWorkflow: controller state consistent across replay
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries
  • AbortController abortReasonTypesWorkflow: various abort reason types propagate correctly
  • AbortController abortFetchInFlightWorkflow: aborting cancels an in-flight fetch
  • AbortController abortVoidSleepTimeoutWorkflow: documented void sleep().then(abort) pattern works
  • AbortController abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step
  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [hook-first-hook-first]: hook.then → addEventListener → resumeHook → abort()
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_41KZ53C06S0GMQGQHBNW4A957P | 🔍 observability
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_41KZ53C2PT0GW3VJA24MSZSXCW | 🔍 observability
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41KZ53C5630GSM6FF8WM7TFN15 | 🔍 observability
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T | 🔍 observability
  • distributedAbortController - reconnect to existing controller | wrun_41KZ53CR8G0GN5R7BBVBNRX1BM | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

nextjs-webpack-quickjs (25 failed):

  • hookWithSleepFinalStepWorkflow - step only on final payload | wrun_41KZ536Z1K0GWK01YQV3Z3H9SB | 🔍 observability
  • sleepInLoopWorkflow - sleep inside loop with steps actually delays each iteration | wrun_41KZ5376YA0GVQAAGRMF0YXS7R | 🔍 observability
  • sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) | wrun_41KZ537GMH0GY2MYSA6W6K3T29 | 🔍 observability
  • AbortController abortTimeoutWorkflow: timeout cancels long-running step
  • AbortController abortParallelWorkflow: abort cancels all parallel steps
  • AbortController abortFromStepWorkflow: step abort cancels an in-flight sibling step
  • AbortController abortAlreadyAbortedWorkflow: pre-aborted signal seen by step
  • AbortController abortReasonWorkflow: abort reason preserved across boundaries
  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortViaHookWorkflow: external hook triggers abort on in-flight step
  • AbortController abortExternalSignalInFlightWorkflow: external abort fires mid-flight, propagates to nested steps
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM
  • AbortController abortAnyInStepWorkflow: AbortSignal.any inside a step composes deserialized signals
  • AbortController abortSurvivesReplayWorkflow: controller state consistent across replay
  • AbortController abortFetchInFlightWorkflow: aborting cancels an in-flight fetch
  • AbortController abortDeterministicBranchWorkflow: if-check takes same path on first-run and replay
  • AbortController abortListenerWorkflow: signal.addEventListener fires on the deserialized step signal
  • AbortController abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step
  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [hook-first-hook-first]: hook.then → addEventListener → resumeHook → abort()
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_41KZ53C2PT0GW3VJA24MSZSXCW | 🔍 observability
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run

nitro-node (14 failed):

  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step
  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_41KZ53C06S0GMQGQHBNW4A957P | 🔍 observability
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_41KZ53C2PT0GW3VJA24MSZSXCW | 🔍 observability
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36 | 🔍 observability
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T | 🔍 observability
  • distributedAbortController - reconnect to existing controller | wrun_41KZ53CR8G0GN5R7BBVBNRX1BM | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • 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

nitro-quickjs (12 failed):

  • AbortController abortSurvivesReplayWorkflow: controller state consistent across replay
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries
  • AbortController abortReasonTypesWorkflow: various abort reason types propagate correctly
  • AbortController abortFetchUncaughtWorkflow: uncaught fetch AbortError is FatalError, no retries
  • AbortController abortFetchInFlightWorkflow: aborting cancels an in-flight fetch
  • AbortController abortVoidSleepTimeoutWorkflow: documented void sleep().then(abort) pattern works
  • AbortController abortDeterministicBranchWorkflow: if-check takes same path on first-run and replay
  • AbortController abortListenerWorkflow: signal.addEventListener fires on the deserialized step signal
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_41KZ53C2PT0GW3VJA24MSZSXCW | 🔍 observability
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T | 🔍 observability

nuxt-node (14 failed):

  • hookAdoptOwnerResultWorkflow - duplicate adopts the owner result via conflict.returnValue | wrun_41KZ530TMJ0GXG39HEVK97G55J | 🔍 observability
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_41KZ53C06S0GMQGQHBNW4A957P | 🔍 observability
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_41KZ53C2PT0GW3VJA24MSZSXCW | 🔍 observability
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41KZ53C5630GSM6FF8WM7TFN15 | 🔍 observability
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36 | 🔍 observability
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T | 🔍 observability
  • distributedAbortController - TTL expiration triggers signal | wrun_41KZ53CGYY0GPFXKPCA7PA0AY7 | 🔍 observability
  • distributedAbortController - reconnect to existing controller | wrun_41KZ53CR8G0GN5R7BBVBNRX1BM | 🔍 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 workflow throws after awaited setAttributes: attribute still persists on the failed run
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

nuxt-quickjs (17 failed):

  • AbortController abortViaHookWorkflow: external hook triggers abort on in-flight step
  • AbortController abortExternalSignalWorkflow: signal passed as workflow input
  • AbortController abortExternalSignalInFlightWorkflow: external abort fires mid-flight, propagates to nested steps
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM
  • AbortController abortAnyInStepWorkflow: AbortSignal.any inside a step composes deserialized signals
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries
  • AbortController abortFetchInFlightWorkflow: aborting cancels an in-flight fetch
  • AbortController abortDeterministicBranchWorkflow: if-check takes same path on first-run and replay
  • AbortController abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step
  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_41KZ53C06S0GMQGQHBNW4A957P | 🔍 observability
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T | 🔍 observability
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

sveltekit-node (17 failed):

  • AbortController abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires
  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [hook-first-hook-first]: hook.then → addEventListener → resumeHook → abort()
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_41KZ53C06S0GMQGQHBNW4A957P | 🔍 observability
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_41KZ53C2PT0GW3VJA24MSZSXCW | 🔍 observability
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36 | 🔍 observability
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T | 🔍 observability
  • distributedAbortController - TTL expiration triggers signal | wrun_41KZ53CGYY0GPFXKPCA7PA0AY7 | 🔍 observability
  • distributedAbortController - reconnect to existing controller | wrun_41KZ53CR8G0GN5R7BBVBNRX1BM | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • 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
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

sveltekit-quickjs (30 failed):

  • sleepWinsRaceWorkflow | wrun_41KZ52PWD70GKYC7E26CPE07Z5 | 🔍 observability
  • AbortController abortAlreadyAbortedWorkflow: pre-aborted signal seen by step
  • AbortController abortReasonWorkflow: abort reason preserved across boundaries
  • AbortController abortAfterCompletionWorkflow: abort after step completes is a no-op
  • AbortController abortViaHookWorkflow: external hook triggers abort on in-flight step
  • AbortController abortExternalSignalWorkflow: signal passed as workflow input
  • AbortController abortExternalSignalInFlightWorkflow: external abort fires mid-flight, propagates to nested steps
  • AbortController abortReasonTypesWorkflow: various abort reason types propagate correctly
  • AbortController abortFetchUncaughtWorkflow: uncaught fetch AbortError is FatalError, no retries
  • AbortController abortFetchInFlightWorkflow: aborting cancels an in-flight fetch
  • AbortController abortDeterministicBranchWorkflow: if-check takes same path on first-run and replay
  • AbortController abortListenerWorkflow: signal.addEventListener fires on the deserialized step signal
  • AbortController abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step
  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [hook-first-hook-first]: hook.then → addEventListener → resumeHook → abort()
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_41KZ53C06S0GMQGQHBNW4A957P | 🔍 observability
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41KZ53C5630GSM6FF8WM7TFN15 | 🔍 observability
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36 | 🔍 observability
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T | 🔍 observability
  • distributedAbortController - TTL expiration triggers signal | wrun_41KZ53CGYY0GPFXKPCA7PA0AY7 | 🔍 observability
  • distributedAbortController - reconnect to existing controller | wrun_41KZ53CR8G0GN5R7BBVBNRX1BM | 🔍 observability
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes setAttributesWorkflow: workflow-body calls append native attr_set events and merge correctly
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes Promise.all of disjoint-key writes: every key lands
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

vite-node (7 failed):

  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries
  • AbortController abortReasonTypesWorkflow: various abort reason types propagate correctly
  • AbortController abortFetchUncaughtWorkflow: uncaught fetch AbortError is FatalError, no retries
  • AbortController abortFetchInFlightWorkflow: aborting cancels an in-flight fetch
  • AbortController abortDeterministicBranchWorkflow: if-check takes same path on first-run and replay
  • AbortController abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires
  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook

vite-quickjs (28 failed):

  • sleepWinsRaceWorkflow | wrun_41KZ52PWD70GKYC7E26CPE07Z5 | 🔍 observability
  • AbortController abortSurvivesReplayWorkflow: controller state consistent across replay
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries
  • AbortController abortReasonTypesWorkflow: various abort reason types propagate correctly
  • AbortController abortFetchInFlightWorkflow: aborting cancels an in-flight fetch
  • AbortController abortVoidSleepTimeoutWorkflow: documented void sleep().then(abort) pattern works
  • AbortController abortDeterministicBranchWorkflow: if-check takes same path on first-run and replay
  • AbortController abortListenerWorkflow: signal.addEventListener fires on the deserialized step signal
  • AbortController abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step
  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [hook-first-hook-first]: hook.then → addEventListener → resumeHook → abort()
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_41KZ53C06S0GMQGQHBNW4A957P | 🔍 observability
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_41KZ53C2PT0GW3VJA24MSZSXCW | 🔍 observability
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41KZ53C5630GSM6FF8WM7TFN15 | 🔍 observability
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36 | 🔍 observability
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T | 🔍 observability
  • distributedAbortController - TTL expiration triggers signal | wrun_41KZ53CGYY0GPFXKPCA7PA0AY7 | 🔍 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 fire-and-forget: void setAttributes lands without awaiting
  • setAttributes Promise.all of disjoint-key writes: every key lands
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

💻 Local Development (2 failed)

astro-stable-node (1 failed):

  • webhookWorkflow | wrun_41KZ52NHBK0GGP2TBXVVQ9FTZN

sveltekit-stable-node (1 failed):

  • webhookWorkflow | wrun_41KZ52NHBK0GGP2TBXVVQ9FTZN

📦 Local Production (1 failed)

sveltekit-stable-node (1 failed):

  • webhookWorkflow | wrun_41KZ52NHBK0GGP2TBXVVQ9FTZN
📋 Other (61 failed)

e2e-vercel-prod-nest-node (8 failed):

  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step
  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_41KZ53C06S0GMQGQHBNW4A957P
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes Promise.all of disjoint-key writes: every key lands
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

e2e-vercel-prod-nest-quickjs (21 failed):

  • sleepWinsRaceWorkflow | wrun_41KZ52PWD70GKYC7E26CPE07Z5
  • AbortController abortAnyInWorkflowWorkflow: AbortSignal.any composes signals inside the workflow VM
  • AbortController abortAnyInStepWorkflow: AbortSignal.any inside a step composes deserialized signals
  • AbortController abortSurvivesReplayWorkflow: controller state consistent across replay
  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries
  • AbortController abortReasonTypesWorkflow: various abort reason types propagate correctly
  • AbortController abortFetchUncaughtWorkflow: uncaught fetch AbortError is FatalError, no retries
  • AbortController abortFetchInFlightWorkflow: aborting cancels an in-flight fetch
  • AbortController abortDeterministicBranchWorkflow: if-check takes same path on first-run and replay
  • AbortController abortListenerWorkflow: signal.addEventListener fires on the deserialized step signal
  • AbortController abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step
  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_41KZ53C06S0GMQGQHBNW4A957P
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_41KZ53C2PT0GW3VJA24MSZSXCW
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41KZ53C5630GSM6FF8WM7TFN15
  • distributedAbortController - TTL expiration triggers signal | wrun_41KZ53CGYY0GPFXKPCA7PA0AY7
  • distributedAbortController - reconnect to existing controller | wrun_41KZ53CR8G0GN5R7BBVBNRX1BM
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes

e2e-vercel-prod-tanstack-start-node (18 failed):

  • AbortController abortVoidSleepTimeoutWorkflow: documented void sleep().then(abort) pattern works
  • AbortController abortDeterministicBranchWorkflow: if-check takes same path on first-run and replay
  • AbortController abortListenerWorkflow: signal.addEventListener fires on the deserialized step signal
  • AbortController abortThrowIfAbortedMidFlightWorkflow: throwIfAborted in a polling loop bails when abort fires
  • AbortController abortDeterministicBranchFromStepWorkflow: branches stay consistent when abort comes from a step
  • AbortController abortHookOrderingWorkflow [listener-first-abort-first]: addEventListener → hook.then → abort() → resumeHook
  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()
  • AbortController abortHookOrderingWorkflow [hook-first-abort-first]: hook.then → addEventListener → abort() → resumeHook
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_41KZ53C06S0GMQGQHBNW4A957P
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41KZ53C5630GSM6FF8WM7TFN15
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T
  • distributedAbortController - reconnect to existing controller | wrun_41KZ53CR8G0GN5R7BBVBNRX1BM
  • 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 fire-and-forget: void setAttributes lands without awaiting
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run

e2e-vercel-prod-tanstack-start-quickjs (14 failed):

  • AbortController abortHookOrderingWorkflow [listener-first-hook-first]: addEventListener → hook.then → resumeHook → abort()
  • AbortController abortHookOrderingWorkflow [hook-first-hook-first]: hook.then → addEventListener → resumeHook → abort()
  • importMetaUrlWorkflow - import.meta.url is available in step bundles | wrun_41KZ53C06S0GMQGQHBNW4A957P
  • metadataFromHelperWorkflow - getWorkflowMetadata/getStepMetadata work from module-level helper (#1577) | wrun_41KZ53C2PT0GW3VJA24MSZSXCW
  • resilient start: addTenWorkflow completes when run_created returns 500 | wrun_41KZ53C5630GSM6FF8WM7TFN15
  • getterStepWorkflow - getter functions with "use step" directive | wrun_41KZ53C7XM0GPZVQNMH70KVJ36
  • distributedAbortController - manual abort triggers signal | wrun_41KZ53CE4N0GTJRD9TVKKCZK6T
  • setAttributes start: initial attributes are seeded on run creation
  • setAttributes start: reserved-prefix initial attributes are seeded with allowReservedAttributes
  • setAttributes setAttributesInsideStepWorkflow: step-body calls append attributed native events
  • setAttributes fire-and-forget: void setAttributes lands without awaiting
  • setAttributes Promise.all of disjoint-key writes: every key lands
  • setAttributes workflow throws after awaited setAttributes: attribute still persists on the failed run
  • setAttributes validation DX: invalid writes throw catchable FatalErrors naming rule and limit

E2E Test Summary

Summary
Passed Failed Skipped Total
❌ ▲ Vercel Production 2560 372 478 3410
❌ 💻 Local Development 2973 2 435 3410
❌ 📦 Local Production 3265 1 454 3720
✅ 🐘 Local Postgres 3266 0 454 3720
✅ 🪟 Windows 155 0 0 155
❌ 📋 Other 1995 61 424 2480
✅ vercel-multi-region 27 0 0 27
Total 14241 436 2245 16922
Details by Category

❌ ▲ Vercel Production

App Passed Failed Skipped
❌ astro-node 115 12 28
❌ astro-quickjs 111 16 28
❌ example-node 112 15 28
❌ example-quickjs 115 12 28
❌ express-node 116 11 28
❌ express-quickjs 105 22 28
❌ fastify-node 117 10 28
❌ fastify-quickjs 110 17 28
❌ hono-node 115 12 28
❌ hono-quickjs 109 18 28
❌ nextjs-turbopack-node 132 20 3
❌ nextjs-turbopack-quickjs 134 18 3
❌ nextjs-webpack-node 127 25 3
❌ nextjs-webpack-quickjs 127 25 3
❌ nitro-node 113 14 28
❌ nitro-quickjs 115 12 28
❌ nuxt-node 113 14 28
❌ nuxt-quickjs 110 17 28
❌ sveltekit-node 129 17 9
❌ sveltekit-quickjs 116 30 9
❌ vite-node 120 7 28
❌ vite-quickjs 99 28 28

❌ 💻 Local Development

App Passed Failed Skipped
❌ astro-stable-node 128 1 26
✅ astro-stable-quickjs 129 0 26
✅ express-stable-node 129 0 26
✅ express-stable-quickjs 129 0 26
✅ fastify-stable-node 129 0 26
✅ fastify-stable-quickjs 129 0 26
✅ hono-stable-node 129 0 26
✅ hono-stable-quickjs 129 0 26
✅ nextjs-turbopack-canary-node 136 0 19
✅ nextjs-turbopack-canary-quickjs 136 0 19
✅ nextjs-turbopack-stable-quickjs 155 0 0
✅ nextjs-webpack-canary-quickjs 136 0 19
✅ nextjs-webpack-stable-node 155 0 0
✅ nextjs-webpack-stable-quickjs 155 0 0
✅ nitro-stable-node 129 0 26
✅ nitro-stable-quickjs 129 0 26
✅ nuxt-stable-node 129 0 26
✅ nuxt-stable-quickjs 129 0 26
❌ sveltekit-stable-node 147 1 7
✅ sveltekit-stable-quickjs 148 0 7
✅ vite-stable-node 129 0 26
✅ vite-stable-quickjs 129 0 26

❌ 📦 Local Production

App Passed Failed Skipped
✅ astro-stable-node 129 0 26
✅ astro-stable-quickjs 129 0 26
✅ express-stable-node 129 0 26
✅ express-stable-quickjs 129 0 26
✅ fastify-stable-node 129 0 26
✅ fastify-stable-quickjs 129 0 26
✅ hono-stable-node 129 0 26
✅ hono-stable-quickjs 129 0 26
✅ nextjs-turbopack-canary-node 136 0 19
✅ nextjs-turbopack-canary-quickjs 136 0 19
✅ nextjs-turbopack-stable-node 155 0 0
✅ nextjs-turbopack-stable-quickjs 155 0 0
✅ nextjs-webpack-canary-node 136 0 19
✅ nextjs-webpack-canary-quickjs 136 0 19
✅ nextjs-webpack-stable-node 155 0 0
✅ nextjs-webpack-stable-quickjs 155 0 0
✅ nitro-stable-node 129 0 26
✅ nitro-stable-quickjs 129 0 26
✅ nuxt-stable-node 129 0 26
✅ nuxt-stable-quickjs 129 0 26
❌ sveltekit-stable-node 147 1 7
✅ sveltekit-stable-quickjs 148 0 7
✅ vite-stable-node 129 0 26
✅ vite-stable-quickjs 129 0 26

✅ 🐘 Local Postgres

App Passed Failed Skipped
✅ astro-stable-node 129 0 26
✅ astro-stable-quickjs 129 0 26
✅ express-stable-node 129 0 26
✅ express-stable-quickjs 129 0 26
✅ fastify-stable-node 129 0 26
✅ fastify-stable-quickjs 129 0 26
✅ hono-stable-node 129 0 26
✅ hono-stable-quickjs 129 0 26
✅ nextjs-turbopack-canary-node 136 0 19
✅ nextjs-turbopack-canary-quickjs 136 0 19
✅ nextjs-turbopack-stable-node 155 0 0
✅ nextjs-turbopack-stable-quickjs 155 0 0
✅ nextjs-webpack-canary-node 136 0 19
✅ nextjs-webpack-canary-quickjs 136 0 19
✅ nextjs-webpack-stable-node 155 0 0
✅ nextjs-webpack-stable-quickjs 155 0 0
✅ nitro-stable-node 129 0 26
✅ nitro-stable-quickjs 129 0 26
✅ nuxt-stable-node 129 0 26
✅ nuxt-stable-quickjs 129 0 26
✅ sveltekit-stable-node 148 0 7
✅ sveltekit-stable-quickjs 148 0 7
✅ vite-stable-node 129 0 26
✅ vite-stable-quickjs 129 0 26

✅ 🪟 Windows

App Passed Failed Skipped
✅ nextjs-turbopack-node 155 0 0

❌ 📋 Other

App Passed Failed Skipped
✅ e2e-local-dev-nest-stable-node 129 0 26
✅ e2e-local-dev-nest-stable-quickjs 129 0 26
✅ e2e-local-dev-tanstack-start-node 129 0 26
✅ e2e-local-dev-tanstack-start-quickjs 129 0 26
✅ e2e-local-postgres-nest-stable-node 129 0 26
✅ e2e-local-postgres-nest-stable-quickjs 129 0 26
✅ e2e-local-postgres-tanstack-start-node 129 0 26
✅ e2e-local-postgres-tanstack-start-quickjs 129 0 26
✅ e2e-local-prod-nest-stable-node 129 0 26
✅ e2e-local-prod-nest-stable-quickjs 129 0 26
✅ e2e-local-prod-tanstack-start-node 129 0 26
✅ e2e-local-prod-tanstack-start-quickjs 129 0 26
❌ e2e-vercel-prod-nest-node 119 8 28
❌ e2e-vercel-prod-nest-quickjs 106 21 28
❌ e2e-vercel-prod-tanstack-start-node 109 18 28
❌ e2e-vercel-prod-tanstack-start-quickjs 113 14 28

✅ vercel-multi-region

App Passed Failed Skipped
✅ nextjs-turbopack 27 0 0

📋 View full workflow run

@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

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

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

Comment thread packages/world-local/src/storage/snapshots-storage.ts
…torage

The security bot flagged path traversal: an unvalidated runId was
interpolated directly into {basedir}/snapshots paths, letting ../ (or /,
\, NUL, absolute fragments) escape the snapshots dir on save/load/delete.
Apply the same two-layer scheme as the other world-local storages:
assertSafeEntityId as the primary defense plus resolveWithinBase
containment at the join. Hostile-runId test covers all three operations.
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

commit d2a115b · Tue, 04 Aug 2026 00:41:43 GMT · run logs

Backend: vercel · app: nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1243 (+81%) 🔻 1331 🔴 (+39%) 🔻 1348 🔴 (+36%) 🔻 1412 🔴 (-14%) 30
TTFS stream 1260 (+36%) 🔻 1321 🔴 (+35%) 🔻 1331 🔴 (+34%) 🔻 1351 🔴 (+32%) 🔻 30
TTFS hook + stream 1576 (+339%) 🔻 1689 🔴 (+39%) 🔻 1744 🔴 (+32%) 🔻 1951 🔴 (+14%) 30
STSO 1020 steps (inline) 99 (+18%) 🔻 132 (-14%) 152 (-16%) 💚 229 (-31%) 💚 1018
STSO 1020 steps (queue-hop) 3081 (+48%) 🔻 3081 (+48%) 🔻 3081 (+48%) 🔻 3081 (+48%) 🔻 1
WO 1020 steps 134305 (-14%) 134305 (-14%) 134305 (-14%) 134305 (-14%) 1
SL stream latency 85 (-7.6%) 187 🔴 (-22%) 💚 257 🔴 (-60%) 💚 632 🔴 (-59%) 💚 30
SO stream overhead (text) 109 (-19%) 💚 176 (-32%) 💚 419 (+31%) 🔻 1250 🔴 (+198%) 🔻 30
SO stream overhead (structured) 110 (-9.1%) 159 (-28%) 💚 182 (-46%) 💚 679 (+7.9%) 30
📈 STSO distribution vs main (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: main 152131ms → this run 129941ms (Δ -22190ms, -15%)

   50-100 ms  ┃                         main   3  this   1    -2
  100-150 ms  ███████████████████░░░░┃  main 713  this 902  +189
  150-200 ms  █┃████                    main 237  this  89  -148
  200-250 ms  ┃                         main  40  this  18   -22
  250-300 ms  ┃                         main  10  this   4    -6
  300-350 ms  ┃                         main   6  this   3    -3
  350-400 ms  ┃                         main   4  this   1    -3
  450-500 ms  ┃                         main   1  this   0    -1
  550-600 ms  ┃                         main   1  this   0    -1
  600-650 ms  ┃                         main   1  this   0    -1
1000-1050 ms  ┃                         main   1  this   0    -1
3350-3400 ms  ┃                         main   1  this   0    -1

1020 steps (queue-hop)

Cumulative STSO time: main 2084ms → this run 3081ms (Δ +997ms, +48%)

2000-2500 ms  ┃███████████████████████  main 1  this 0  -1
3000-3500 ms  ░░░░░░░░░░░░░░░░░░░░░░░┃  main 0  this 1  +1
📜 Previous results (1)

968cfa1

Fri, 31 Jul 2026 23:24:59 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
STSO 1020 steps (inline) 166 (+29%) 🔻 452 (±0%) 503 (-1.0%) 693 (-3.3%) 1016
STSO 1020 steps (queue-hop) 2271 (+52%) 🔻 3077 (-2.5%) 3077 (-2.5%) 3077 (-2.5%) 3
WO 1020 steps 380806 (-2.4%) 380806 (-2.4%) 380806 (-2.4%) 380806 (-2.4%) 1
SO stream overhead (structured) 103 (+1.0%) 157 (-17%) 💚 174 (-26%) 💚 249 (-62%) 💚 30
ℹ️ Metric definitions & methodology

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

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

Metrics — TTFS: time to first step body (in-deployment start() → first step body, deployment clocks) · STSO: step-to-step overhead (gap between consecutive step bodies) · WO: workflow overhead (whole-run time outside step bodies, in-deployment anchored) · SL: stream latency (in-deployment write → read propagation, readAt - writtenAt) · SO: stream overhead (end-to-end write+consume time beyond the modelled generation window)

Scenarios — step: one trivial no-op step, no stream; no hooks, so the run stays in turbo mode (in-process fast path) · stream: one streaming step; no hooks, so the run stays in turbo mode (in-process fast path) · hook + stream: registers a hook before one step, which exits turbo mode (dispatch path) · 1020 steps: 1020 trivial sequential steps; STSO is measured between consecutive steps in the given step ranges, and WO is the whole-run overhead outside step bodies · stream latency: parallel reader/writer steps on a dedicated stream; SL is the in-deployment write->read propagation (readAt - writtenAt) · stream overhead (text): writer streams 300 variable-length text token deltas paced at 100/s for 3s (a haiku-size LLM's token throughput) while a parallel reader drains the whole stream; SO is the end-to-end write+consume time beyond the 3s generation window (overhead/backpressure) · stream overhead (structured): same workload as stream overhead (text), but each delta is an AI-SDK-style structured object ({ type: 'text-delta', id, text }) instead of a raw string, so the SO gap vs the text scenario is the added serialization cost

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

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

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

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

Reviewed the incremental diff (16 files, +838). Mergeable with fixes — nothing here runs until #3251, but three inline findings become live correctness bugs the moment it does, and the changeset/semver one ships breakage to community worlds on merge.

Beyond the inline comments:

  • Orphaned snapshots on postgres cleanup: the legacy run_cancelled path in storage.ts deletes hooks and waits but not snapshots; there are zero FKs/cascades in schema.ts and no retention job, and rows are 5–15 MB bytea — unbounded growth. Other cleanup sites worth checking: storage.ts:948, 1000, 1045, 1601.
  • All three vercel paths bypass makeRequest, losing transient-error → WorkflowWorldError{code:'TRANSPORT'} mapping (retryability), getRequestTimeoutMs(), and the http logging. The undici.request() choice for save is well-justified (undici#3288 — Buffer body survives RetryAgent retries) but argues for teaching makeRequest a raw-body mode, not three hand-rolled paths.
  • Older-server compat: load's 404→null degrades gracefully by accident, but save/delete throw against a workflow-server without the endpoints — #3251 must treat save as best-effort or this becomes a hard version coupling.
  • Minor: world-local snapshots is the only sub-storage not given the tag (breaks multi-world basedir isolation); no load/delete test coverage on the vercel client (the 503-retry save test is genuinely good); nothing exercises the postgres table beyond the migration applying; eventsCursor rides an HTTP header unencoded (constrains future cursor formats).
  • Migration note: it's 0018_add_snapshots_table.sql (the PR description says the stack docs referenced 0016).

Path-traversal hardening in the runId-validation follow-up commit is solid (two independent layers, well-tested) — no findings there.

metadata: SnapshotMetadata
): Promise<void> {
await ensureDir(snapshotsDir);
await Promise.all([

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.

Torn save pair: write() is atomic per file (temp + rename), but this Promise.all of two renames is not — a crash between them, or a concurrent load interleaving, pairs a .bin from one suspension with an eventsCursor from another. That's the worst failure mode for this feature: restore replays from the wrong log position and silently diverges instead of erroring. Suggest a single atomic write (metadata header + data in one file) or data-then-metadata with the metadata carrying a digest of the data it describes, as the commit marker.

const data = new Uint8Array(buffer);

const eventsCursor =
response.headers.get('X-Snapshot-Events-Cursor') || null;

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.

load fabricates metadata instead of failing loudly. (a) save writes metadata.eventsCursor ?? '' (L51), so a server that drops the header is indistinguishable from a snapshot taken at cursor-null — i.e. "replay from the beginning", the silent-wrong-answer direction. (b) createdAt falls back to new Date(), inventing a timestamp. Unlike local/postgres, this path never runs SnapshotMetadataSchema.parse. Treat a missing cursor header as malformed (throw, or return null so the caller cold-starts) and parse through the schema.

};
},

async delete(runId: string): Promise<void> {

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.

Delete throws on 404 while local (force: true) and postgres (plain DELETE) are idempotent. The interface JSDoc says delete runs at terminal state — exactly the path most likely to retry, run twice, or run for a run that never snapshotted. Add 404 to the success set, or document non-idempotence and make #3251 tolerate the throw.

// Diagnostic: actual on-the-wire snapshot bytes and the HTTP-PUT
// cost, grep-able by runId alongside @workflow/core's QUICKJS_VM
// diagnostics.
console.debug('[Workflow] WORLD_SNAPSHOT_DIAG', {

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.

Ungated console.debug on a path that runs on every suspension/resume (also at L139 and the load path). Route through the package's existing httpLog/debug gating.

* data so that on restore, only events created after the snapshot need
* to be fetched.
*/
snapshots: {

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.

Breaking change labeled minor. This is a required member of Storage, and the changeset itself says community worlds must add it — that's a major for @workflow/world. worlds-manifest.json lists 13 out-of-repo implementations (@workflow-worlds/*, @fantasticfour/world-*, @platformatic/world, workflow-world-jazz); all fail to typecheck, and none are in CI, so this is invisible until they upgrade. In-repo mocks dodge it via as unknown as World, which is why CI is green.

Pick deliberately: make it snapshots?: and have #3251 feature-detect (keeps this a true minor, and a world that can't store blobs should be allowed to simply not — #3251 already needs a no-snapshot cold-start path), or keep it required and bump major. I'd favor optional. The bump type also determines the semver step on any stable backport, so the mislabel isn't cosmetic.

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