Skip to content

Use consistent framing for abort streams - #5081

Merged
karthikscale3 merged 2 commits into
mainfrom
codex/frame-abort-streams-20261026
Oct 26, 2026
Merged

karthikscale3 merged 2 commits into
mainfrom
codex/frame-abort-streams-20261026

Conversation

@karthikscale3

@karthikscale3 karthikscale3 commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Description

Internal abort streams currently store a serialized packet directly, while regular object streams wrap each packet in a length header. Standardize new abort writes on [4-byte big-endian length][existing serialized payload] so readers can identify a complete packet even when transport reads split or combine stored chunks.

The runtime reader accepts both existing unframed packets and new framed packets. Framed cancellation propagates as soon as a complete packet arrives, without waiting for stream close or transport teardown. Incomplete packets leave the durable hook fallback intact. Payload encryption, abort reasons, and the hook_received event format stay unchanged.

Cross-deployment start() frames abort packets only when its direct capability probe confirms support. resumeHook() retains unframed abort writes: its existing version metadata describes the starting client and does not independently attest the receiving runtime. Preserve that metadata's established semantics for server admission checks and analytics. Node and QuickJS workflow-initiated aborts, and step-initiated aborts, use framing within their own deployment. Complete legacy packets are decoded after each transport read, so delivery does not wait for a separate best-effort stream close.

Release coordination: the core minor changeset targets 5.3.0, and the framedAbortStreams capability cutoff is 5.3.0. Keep that cutoff aligned with the release that actually ships this reader if another minor release lands first.

How did you test your changes?

  • 703 tests pass across 13 suites using the repository's Vitest configuration on Node 24.13.1: serialization, capabilities, all abort suites, byte-stream framing, start/resume-hook/suspension handlers, step execution, and step context.
  • New cases cover plaintext, symmetric encryption, sealed encryption, byte-by-byte packet splits, coalesced frames, truncated/invalid lengths, receiver-version fallback through real start() and resumeHook(), unchanged durable hook payloads, and cancellation without awaiting a slow stream teardown.
  • Regression checks against the previous implementation fail for legacy delivery without EOF, QuickJS framing, and client-version preservation; the updated implementation passes.
  • Core typecheck passes after generating the normal version/QuickJS assets and building current @workflow/world declarations. Local validation reuses installed dependencies; full monorepo builds and deployed end-to-end tests are left to CI.
  • Biome passes with complexity warnings; changeset validation and whitespace checks pass.

PR Checklist - Required to merge

  • 📦 A minor changeset for @workflow/core is included.
  • 🔒 DCO sign-off is included in the commit.
  • 📝 Maintainer review, including release cutoff coordination.

Signed-off-by: Karthik Kalyanaraman <karthik.kalyanaraman@vercel.com>
@karthikscale3
karthikscale3 requested a review from a team as a code owner October 26, 2026 20:40
@vercel

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

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

🧪 E2E Test Results

✅ All tests passed

⚠️ 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.

  • promiseRaceWorkflow (nitro · 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.

  • run-pickup-stall · hookCleanupTestWorkflow - hook token reuse after workflow completion (nextjs-webpack) · at 21:45:38Z · abandoned wrun_01M5X2SQXJR2WW5RZEEMXJE81J

E2E Test Summary

Summary
Passed Failed Skipped Total
✅ ▲ Vercel Production 3982 0 878 4860
✅ 💻 Local Development 4669 0 551 5220
✅ 📦 Local Production 4669 0 551 5220
✅ 🐘 Local Postgres 4669 0 551 5220
✅ 🪟 Windows 348 0 12 360
✅ 🌐 Cross-language Conformance 68 0 87 155
✅ dynamic-runs 0 0 0 0
✅ vercel-http-transport 897 0 183 1080
✅ vercel-multi-region 27 0 0 27
✅ vercel-ws-transport 607 0 113 720
Total 19936 0 2926 22862
Details by Category

✅ ▲ Vercel Production

App Passed Failed Skipped
✅ astro-node 145 0 35
✅ astro-quickjs 145 0 35
✅ example-node 145 0 35
✅ example-quickjs 145 0 35
✅ express-node 145 0 35
✅ express-quickjs 145 0 35
✅ fastify-node 145 0 35
✅ fastify-quickjs 145 0 35
✅ hono-node 145 0 35
✅ hono-quickjs 145 0 35
✅ nest-node 145 0 35
✅ nest-quickjs 145 0 35
✅ nextjs-turbopack-node 172 0 8
✅ nextjs-turbopack-quickjs 172 0 8
✅ nextjs-webpack-node 172 0 8
✅ nextjs-webpack-quickjs 172 0 8
✅ nitro-node 145 0 35
✅ nitro-quickjs 145 0 35
✅ nuxt-node 145 0 35
✅ nuxt-quickjs 145 0 35
✅ python-node 66 0 114
✅ sveltekit-node 164 0 16
✅ sveltekit-quickjs 164 0 16
✅ tanstack-start-node 145 0 35
✅ tanstack-start-quickjs 145 0 35
✅ vite-node 145 0 35
✅ vite-quickjs 145 0 35

✅ 💻 Local Development

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

✅ 📦 Local Production

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

✅ 🐘 Local Postgres

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

✅ 🪟 Windows

App Passed Failed Skipped
✅ nextjs-turbopack-node 174 0 6
✅ nextjs-turbopack-quickjs 174 0 6

✅ 🌐 Cross-language Conformance

App Passed Failed Skipped
✅ python 68 0 87

✅ dynamic-runs

App Passed Failed Skipped
✅ astro-vercel 0 0 0
✅ example-vercel 0 0 0
✅ express-vercel 0 0 0
✅ fastify-vercel 0 0 0
✅ hono-vercel 0 0 0
✅ nest-vercel 0 0 0
✅ nextjs-turbopack-vercel 0 0 0
✅ nextjs-webpack-vercel 0 0 0
✅ nitro-vercel 0 0 0
✅ nuxt-vercel 0 0 0
✅ sveltekit-vercel 0 0 0
✅ tanstack-start-vercel 0 0 0
✅ vite-vercel 0 0 0

✅ vercel-http-transport

App Passed Failed Skipped
✅ example 145 0 35
✅ express 145 0 35
✅ hono 145 0 35
✅ nextjs-turbopack 172 0 8
✅ nitro 145 0 35
✅ vite 145 0 35

✅ vercel-multi-region

App Passed Failed Skipped
✅ nextjs-turbopack 27 0 0

✅ vercel-ws-transport

App Passed Failed Skipped
✅ example 145 0 35
✅ express 145 0 35
✅ nextjs-turbopack 172 0 8
✅ vite 145 0 35

📋 View full workflow run

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

📊 Workflow Benchmarks

commit 6387510 · Mon, 26 Oct 2026 21:48:14 GMT · run logs

Backend: vercel · app: nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1735 (+43%) 🔻 2006 🔴 (+32%) 🔻 2035 🔴 (+32%) 🔻 2101 🔴 (-16%) 💚 30
TTFS stream 1565 (+13%) 2029 🔴 (+37%) 🔻 2065 🔴 (+36%) 🔻 2128 🔴 (+36%) 🔻 30
TTFS hook + stream 2153 (+29%) 🔻 2268 🔴 (+29%) 🔻 2319 🔴 (+30%) 🔻 2523 🔴 (+35%) 🔻 30
Fan-out TTFS Promise.all(100 steps) 579 (+11%) 861 (+26%) 🔻 895 (+6.0%) 2811 (+61%) 🔻 10
Fan-out TTLS Promise.all(100 steps) 2577 (+99%) 🔻 4228 (+1.6%) 4275 (-1.0%) 8643 (+45%) 🔻 10
STSO 1020 steps (inline) 76 (+52%) 🔻 106 (+22%) 🔻 126 (+21%) 🔻 214 (-35%) 💚 1019
WO 1020 steps 106484 (+17%) 🔻 106484 (+17%) 🔻 106484 (+17%) 🔻 106484 (+17%) 🔻 1
📈 STSO distribution vs main (inline / queue-hop histograms)

1020 steps (inline)

Cumulative STSO time: main 90710ms → this run 106182ms (Δ +15472ms, +17%)

 50-100 ms  ███████████████┃████████  main 894  this 612  -282
100-150 ms  ██░░░░░░░┃                main  86  this 370  +284
150-200 ms  ┃                         main  15  this  24    +9
200-250 ms  ┃                         main   7  this   8    +1
250-300 ms  ┃                         main   5  this   2    -3
300-350 ms  ┃                         main   4  this   1    -3
350-400 ms  ┃                         main   4  this   1    -3
400-450 ms  ┃                         main   3  this   1    -2
450-500 ms  ┃                         main   1  this   0    -1
📜 Previous results (1)

53a4ffe

Mon, 26 Oct 2026 20:56:29 GMT · run logs

vercel / nextjs-turbopack

Metric Scenario Best (ms) P75 (ms) P90 (ms) P99 (ms) Samples
TTFS step 1915 (+58%) 🔻 2044 🔴 (+34%) 🔻 2068 🔴 (+34%) 🔻 2313 🔴 (-7.9%) 30
TTFS stream 1667 (+21%) 🔻 2034 🔴 (+38%) 🔻 2053 🔴 (+36%) 🔻 2090 🔴 (+33%) 🔻 30
TTFS hook + stream 664 (-60%) 💚 2314 🔴 (+32%) 🔻 2340 🔴 (+31%) 🔻 2573 🔴 (+38%) 🔻 30
Fan-out TTFS Promise.all(100 steps) 537 (+3.3%) 871 (+28%) 🔻 1031 (+22%) 🔻 2711 (+55%) 🔻 10
Fan-out TTLS Promise.all(100 steps) 2963 (+129%) 🔻 4233 (+1.8%) 4259 (-1.4%) 8536 (+43%) 🔻 10
STSO 1020 steps (inline) 87 (+74%) 🔻 107 (+23%) 🔻 131 (+26%) 🔻 178 (-46%) 💚 1019
WO 1020 steps 107795 (+18%) 🔻 107795 (+18%) 🔻 107795 (+18%) 🔻 107795 (+18%) 🔻 1
ℹ️ Metric definitions & methodology

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.

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)

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

🔴 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. Streaming delivery performance is measured in durabench.

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

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Sim World

Simulated world deterministic testing for races. Traces

🟢 world-sim scenario book — 0 fail of 44 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 11 0ms ok 0
✅ hook-at-step-completed completed 11 0ms ok 0
✅ hook-at-hook-created completed 11 0ms ok 0
✅ claim-hook-at-registration completed 11 0ms ok 0
✅ claim-hook-at-step-started completed 11 0ms ok 0
✅ deadline-hook-wins completed 6 1.0h ok 0
✅ deadline-expires completed 6 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 11 0ms ok 0
✅ peek-hook-before-branch completed 11 0ms ok 0
✅ peek-hook-after-branch completed 11 0ms ok 0
✅ peek-hook-at-registration completed 11 0ms ok 0
✅ race-hook-before-probe completed 11 0ms ok 0
✅ race-hook-after-probe completed 11 0ms ok 0
✅ race-duplicate-delivery completed 12 0ms ok 0
✅ attr-hook-before-step completed 10 0ms ok 0
✅ attr-hook-after-step completed 10 0ms ok 0
✅ attr-from-step-body completed 12 0ms ok 0
✅ fork-hook-after-timeout completed 13 1.0m ok 0
✅ fork-hook-before-timeout completed 13 1.0m ok 0
✅ count-hook-after-timeout completed 16 1.0m ok 0
✅ count-hook-before-timeout completed 19 1.0m ok 0
✅ stale-read-step-count-fork completed 19 1.0m ok 0
✅ stale-read-equal-step-counts completed 13 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 16 1.0m ok 0
✅ in-flight-before-decision-counted completed 16 1.0m ok 0
✅ in-flight-after-decision completed 18 2.0m ok 0
✅ stale-read-step-count-fork-fenced completed 19 1.0m ok 0
✅ fork-hook-wins completed 12 1.0m ok 0
✅ fork-timeout-wins completed 12 1.0m ok 0
✅ unclaimed-payload-under-fork completed 16 1.0m ok 0
✅ claimed-payload-under-fork completed 16 1.0m ok 0
✅ writers-independent-step-bodies completed 12 0ms ok 0
✅ writers-scripted-tempo completed 11 0ms ok 0
✅ cancel-mid-step cancelled 7 0ms skipped 0

Full trace: world-sim.txt

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor
Framework Flow route Step reg. Framework output
hono 267.6 KiB (±0) 95.7 KiB (+160 B) 2.00 MiB (+970 B)
nextjs-turbopack 275.1 KiB (±0) 426 B (±0) 975.9 KiB (+494 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.

6387510 · run

@alangenfeld alangenfeld left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for digging into this! The UI fixes in #5079 / vercel/front#93733 look like they handle the garbled encrypted previews on their own, so these notes are just about the runtime side.

For weighing impact: on main the step reader takes the first chunk. If a packet ever arrived split or coalesced, hydration would fail and fall back to controller.abort() without a reason, so the abort itself still propagates in real time. What's lost is reason fidelity. Abort packets are small (a serialized { aborted, reason }), so splits should be rare. That's not an argument against fixing it, just context next to the protocol surface this adds (capability cutoff, minor bump, release coordination).

A few inline notes. Some options that might be worth considering, no strong preference:

  • Keep framing, but also frame the QuickJS writer, and have the reader decode unframed packets as soon as they're complete instead of waiting for EOF.
  • Or skip the wire change: keep writers unframed and have the reader accumulate chunks until hydration succeeds. A truncated devalue or AES-GCM payload fails to decode, and abort payloads are below the compression threshold. That would avoid the capability gate entirely.
  • Either way, the executionContext.workflowCoreVersion change in start.ts looks like a real fix in its own right, and might be easier to review as a separate PR.

Local agent review (`anthropic/claude-opus-5.5`)

Comment thread packages/core/src/runtime/start.ts Resolved
Signed-off-by: Karthik Kalyanaraman <karthik.kalyanaraman@vercel.com>
@changeset-bot

changeset-bot Bot commented Oct 26, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6387510

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

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

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

@alangenfeld alangenfeld left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the quick follow-ups. The eager legacy decode, QuickJS framing, and keeping resumeHook() unframed all look good to me.


Local agent review (`anthropic/claude-opus-5.5`)

@karthikscale3
karthikscale3 merged commit e127948 into main Oct 26, 2026
187 of 188 checks passed
@karthikscale3
karthikscale3 deleted the codex/frame-abort-streams-20261026 branch October 26, 2026 21:57
@github-actions

Copy link
Copy Markdown
Contributor

No backport to stable for e127948 (AI decision).

This is a wire-protocol enhancement, not a stability fix: it introduces a new length-prefixed framing for abort stream packets plus a new framedAbortStreams capability flag gated on a 5.3.0 cutoff, carried by a minor changeset for @workflow/core. The release-coordination note ties the capability cutoff to a specific main release, so backporting it would put a new negotiated format (and a version cutoff that does not correspond to any stable release) on the maintenance line. The embedded fix worth noting for a human is the reader change in setupAbortStreamReader/readAbortStreamPacket that delivers a complete legacy unframed packet without waiting for the best-effort stream close and cancels the transport in a finally; that reader-side robustness could be split out and force-backported on its own.

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

e1279482719168a0987e48f73d107be6287670c3

@github-actions github-actions Bot mentioned this pull request Oct 26, 2026
VaguelySerious added a commit that referenced this pull request Oct 26, 2026
…/9 runtime

The branch side of merging origin/main (#5053, #5081): the next commit
merges main in, and these are the edits main makes to pre-split runtime
files, applied where that code lives on this branch.

- #5081: frame abort-stream packets in the shared quickjs-pending-ops.ts
  and suspension-hooks.ts (both spec 8 and spec 9 use them), pass
  framedAbortStreams=true from the spec 9 step executor as main does for
  spec 8, and mirror main's framing test in spec 8 and spec 9
  suspension-handler tests.
- #5053: runSingleFlight takes an optional rerunAfter(winner) predicate;
  spec 8 reruns a loser whose winner came back `skipped`. Spec 9 has no
  `skipped` outcome and its loser redelivers (`throttled`), so it is not
  ported.

This commit imports serialization/abort-stream.ts, which arrives with the
merge that follows it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VaguelySerious added a commit that referenced this pull request Oct 26, 2026
…spec 8

Main's #5081 step-executor change (framedAbortStreams=true for
step-initiated aborts) and #5053 single-flight tests, applied to the
spec8/ copies this branch moved those files to.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
VaguelySerious added a commit that referenced this pull request Oct 26, 2026
Brings in #5041, #5053, #5079, #5081 and #5085.

Conflicts:
- spec8/quickjs-entrypoint.ts, spec8/suspension-handler.ts: kept the
  branch side. The abort-stream writes main framed (#5081) live in the
  shared quickjs-pending-ops.ts and suspension-hooks.ts on this branch,
  so frameAbortStreamPayload is applied there.
- spec8/suspension-handler.test.ts: kept both sides (main's framing test
  plus the branch's import paths).
- step-single-flight.ts: the branch's generic runSingleFlight takes an
  optional rerunAfter(winner) predicate; spec8's wrapper passes
  winner.type === 'skipped', which is main's #5053 fix.

Spec 9 decisions:
- #5081 abort framing: ported. Both specs share quickjs-pending-ops.ts and
  suspension-hooks.ts, so the framed write covers spec 8 and spec 9. The
  spec 9 step executor now passes framedAbortStreams=true like spec 8's
  (same-deployment reader). Main's spec 8 suspension-handler framing test
  is mirrored in spec9/suspension-handler.test.ts; quickjs-lifecycle.test.ts
  already targets the spec 9 entrypoint.
- #5053 skipped single-flight winner: not ported. Spec 9 has no `skipped`
  step outcome, and its loser returns `throttled`, keeping its message and
  redelivering after STEP_SINGLE_FLIGHT_REDELIVERY_SECONDS, so a winner that
  ran nothing cannot strand the step.
- #5041 (serialization.ts), #5079 (web), #5085 (ci), and the #5053
  world-postgres change are outside the spec split; merged as-is.

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

This branch was successfully deployed

1 active deployment
Preview – workflow-docs — 63875109 Deployed Oct 26, 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.

2 participants