Skip to content

[world-local] Land a stream chunk after every chunk already in its stream - #4909

Merged
VaguelySerious merged 4 commits into
vercel:mainfrom
fazxes:world-local-stream-chunk-place
Nov 2, 2026
Merged

VaguelySerious merged 4 commits into
vercel:mainfrom
fazxes:world-local-stream-chunk-place

Conversation

@fazxes

@fazxes fazxes commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Description

world-local names a stream chunk chnk_<ULID> when streams.write() is called, before any await, then writes it to a temporary file and renames that into place. Readers order a stream's chunks by name, and a live reader delivers each chunk it has not had after the ones it already delivered. So a write that stalls between the two, as one does when its process is frozen, or when its file write is slow, lands under a name that sorts ahead of every chunk other writers landed meanwhile:

  • a live reader that already had those chunks gets the late one after them;
  • every read from index 0 gets it before them, so each of those chunks moves one index on, and a reader that resumes from an index (get(name, startIndex), a getChunks cursor) repeats one chunk or misses one.

Two writers of one stream is what happens when a process stalls and another takes its work over. libfx runs durable sessions on world-local and numbers each line of a session's stream by its index, so a reader that reconnects from a line's cursor depends on indexes never moving. libfx's crash campaign on world-local hits this when it freezes a worker with a stream write on its way: the frozen worker's line lands ahead of lines the replacing worker wrote, which a reader in another process already had, and nine later lines change index. The new test below reproduces it without timing: held just before its rename, a chunk minted before two other writers' chunks lands ahead of them.

This change names a chunk as it lands, in the new src/stream-chunk-place.ts:

  • The name is the next one. A chunk's name is the successor, in the ULID alphabet, of the stream's newest chunk id, and the chunk is linked into place with link(), which never replaces a file. The chunk lands only if no other chunk has that name, so it lands after every chunk already in the stream.
  • A writer that stalled after naming its chunk takes the next name. Its link() fails with EEXIST once another writer landed under that name. It lists the stream and tries after the newest. No lock is needed, and none would do: a lock held by a frozen process would hold every writer of the stream until it went stale, and the frozen holder would still land under its old name when it woke.
  • One streamer's writes land in the order they were called, as they did when each chunk was named then. Each write takes its turn in its stream before any await, and lands only after the turns before it, so the guarantee the promise runId test pins is kept. writeMulti still writes its chunks in parallel and lands them in order.
  • No listing in the common case. A streamer remembers the last chunk it landed in each stream and tries the next name first. It lists the stream for its first write to it and after a failed link.
  • A stream's first chunk is chnk_0…01, the successor of the tombstone a zero-retention purge leaves, which still sorts first.
  • A file system that makes no hard links (link() fails with ENOTSUP, EOPNOTSUPP, ENOSYS, EXDEV, or EPERM outside Windows) renames the chunk into place as before, under a name minted as it lands. That narrows the window from the whole write to the moment between naming and renaming.

Why a landed chunk always sorts after every chunk already in the stream: every name a writer creates is the successor of a name that existed, or the first id, so the names after a stream's older chunks have no gap. A link() to the successor of the id a writer knows succeeds only while that successor is free, and with no gap, that means the id it knows is the stream's newest. A writer whose id is out of date fails and lists the stream. Each failure moves its id forward, so the loop ends.

Compatibility:

  • Readers and the file format are unchanged. Names are still chnk_ and 26 ULID characters, sorted as text, and a stream written by an older world-local goes on after its newest chunk.
  • A chunk id no longer carries the time of its write: ids count on from the stream's newest chunk. Nothing in world-local reads that time; streamer.test.ts decodes it only to print the chunks when a test fails.
  • An older world-local writing the same stream at the same time still names its chunks when the write is called, so its own late chunk can still land ahead. Its names carry the current time, while this change's ids count on from an older one, so a streamer with this change that kept the newest id it landed can also land a chunk ahead of one the older version landed since. Either needs both versions writing one stream.

Out of scope: fs.readdir lists a large directory in several system calls, so a reader whose listing runs while two chunks land can see the second without the first, and deliver them out of order. That needs a change on the reader side (wait for the next poll when the next name is not the successor of the last), and is the same before and after this change.

How did you test your changes?

  • New src/stream-chunk-place.test.ts, 7 tests:
    • chunkSuccessor counts up in the ULID alphabet and carries, and refuses what is not a chunk id and the last one;
    • newestChunkId reads the newest chunk of any tag or format and ignores temporary and other files;
    • a chunk whose writer stalled after naming it (held at the link() that lands it, or the rename() that did before this change) lands after the chunks another writer landed meanwhile, the other writer took the name it chose, and a reader in its own streamer gets what a read from index 0 gets;
    • writers in four other processes (src/stream-chunk-place.child.mjs, run with Node's type stripping like in-band-fence.child.mjs), each writing 40 chunks to one stream, land every chunk once, each writer's in its order, and a live reader gets them in the order a read from index 0 does;
    • one streamer's writes land in the order they were called, with the first waiting on its runId promise and a batch whose first chunk is the largest, and the stream's first chunk is chnk_0…01;
    • on a file system that makes no hard links, chunks from two streamers land in order and leave no temporary file, and a streamer with links goes on after them.
  • With streamer.ts back at 5.1.1, the four placement tests fail. The stalled chunk is read before the other writer's: [ 'a1', 'a2', 'b1', 'b2', 'b3' ] where [ 'a1', 'b1', 'b2', 'a2', 'b3' ] is expected. The live reader of the racing processes disagrees with a read from 0. A write lands before the one called earlier. And link is never called.
  • vitest run src in packages/world-local: 28 files, 678 tests pass on 5.1.1 with the change, and 682 on main with it. tsc --noEmit passes. biome check on the changed files reports only the three existing complexity warnings in streamer.ts.
  • End to end, through libfx, which bundles world-local:
    • a model of the stream (TLA+, a takeover with a write on its way) that TLC checks, walked against the code with each chunk's landing held: 40 walks, 656 steps, on world-local with this change, and the walk fails at its third path with 5.1.1, where the stalled chunk is read ahead of the other writer's;
    • libfx's crash scenarios that freeze a worker and let another take its session over, with this change built into world-local: 80 runs, and the reader's view matched a read from 0 in every one. In 6 of them the frozen worker's chunk was on its way when it froze, and it landed after the other worker's. libfx's 60-trial crash campaign on world-local and libfx's bundled copy held every invariant.
  • Cost: on a Mac, a sequential write() takes about 0.6 ms instead of 0.3 ms (the link(), the unlink() of the temporary name, and write()'s own temporary rename), and a streamer's first write to a stream of 2,000 chunks lists it in 2 to 4 ms.

PR Checklist - Required to merge

  • 📦 pnpm changeset was run to create a changelog for this PR
    • Use the correct semver bump type: patch for bug fixes, minor for new features, major for breaking changes.
    • Use pnpm changeset --empty if you are changing documentation or workbench apps
  • 🔒 DCO sign-off passes (run git commit --signoff on your commits)
  • 📝 Ping @vercel/workflow in a comment once the PR is ready, and the above checklist is complete

…ream

Signed-off-by: Pranit <pranit@vercel.com>
@fazxes
fazxes requested a review from a team as a code owner October 18, 2026 15:37
@changeset-bot

changeset-bot Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 617f7d7

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

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

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

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

@vercel

vercel Bot commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

@fazxes is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

Comment thread packages/world-local/src/streamer.ts Resolved

@VaguelySerious VaguelySerious left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(AI) Preliminary review: AI review: no blocking issues


// Without hard links a rename may replace a chunk of the same name, so the
// name is a new ULID unless the stream's newest is later.
async function renamedInto(dir: string, temp: string) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI Review: Note

Two things about the no-hard-links fallback:

  1. It looks unreachable in practice. Every event write already goes through writeExclusive() in fs.ts, which link()s a temp into place and has no fallback. On a file system without hard links, world-local fails at the run's first event, before any stream gets written.
  2. It isn't safe across processes. Say process A lands T|R and B, in the same millisecond, lists T|R and mints T|R' with R' < R. B then takes chunkSuccessor(T|R). A's next write in that millisecond lists T|R too, and its monotonic factory mints T|R+1, which is the same name. Both rename() into chnk_T|R+1, and the second one silently replaces the first's chunk. Before this PR, ids minted in different processes never collided like this.

I'd drop the fallback, along with mint and the monotonicUlid source, and let link() errors surface the way writeExclusive does. If you keep it, a comment noting the overwrite risk would help. Not blocking.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI Review: Note

Addressed in 4caefdc: the fallback is gone.

  • renamedInto, NO_HARD_LINKS, the hardLinks flag and the mint option are removed from stream-chunk-place.ts, and the monotonicUlid source (with its ulid and @workflow/utils imports) is removed from streamer.ts.
  • A link() that fails with anything other than EEXIST, or ENOENT once the directory is recreated, now fails the write. This matches writeExclusive(). The temp file is still cleaned up.
  • The no-hard-links test is replaced by one where link() fails once with ENOTSUP: that write rejects, it leaves no temporary file, and the next write lands as chnk_0…02 right after chnk_0…01.
  • The module comment now says why there is no fallback.

world-local tests pass (679 passed, 4 skipped), tsc --noEmit is clean, and the multi-process SIGSTOP/SIGCONT stress run still holds: the live reader matches a read from 0, and indexes never move.

…d early; write chunk temps directly

- A turn now ends only once the turns before it have, so a write that fails
  before it lands (a bad run id, a failed temp write) no longer lets a later
  write land ahead of an earlier one still waiting on its run id.
- Chunk temporary files are written with a new `writeTempFile()` instead of
  `write()`: no extra temp-and-rename per chunk, and no entry per chunk in
  the never-pruned created-files cache.
- `close()` forgets the stream's newest chunk id, so a streamer does not keep
  one entry per stream it ever wrote.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Peter Wielander <29887157+VaguelySerious@users.noreply.github.com>
@VaguelySerious
VaguelySerious force-pushed the world-local-stream-chunk-place branch from e48c6f7 to 911ccb0 Compare October 23, 2026 22:17
VaguelySerious and others added 2 commits November 2, 2026 11:03
…ems without hard links

The fallback renamed a chunk into place under a minted ULID, or the successor
of the stream's newest id when that was later. Two processes renaming in the
same millisecond could pick the same name, and the second rename silently
replaced the first's chunk. It was also unreachable in practice: every event
write already goes through `writeExclusive()`, which needs `link()` with no
fallback. A link that fails for any reason other than EEXIST or a vanished
directory now fails the write, and leaves no temporary file.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Peter Wielander <29887157+VaguelySerious@users.noreply.github.com>
@VaguelySerious
VaguelySerious force-pushed the world-local-stream-chunk-place branch from 4caefdc to 617f7d7 Compare November 2, 2026 19:06
@VaguelySerious
VaguelySerious merged commit d4b7b06 into vercel:main Nov 2, 2026
123 of 187 checks passed
@github-actions github-actions Bot mentioned this pull request Nov 2, 2026
github-actions Bot added a commit that referenced this pull request Nov 2, 2026
…ream (#4909)

Previously, world-local named a stream chunk `chnk_<ULID>` when `streams.write()` is
called, before any await, then writes it to a temporary file and renames
that into place. Readers order a stream's chunks by name, and a live
reader delivers each chunk it has not had after the ones it already
delivered. So a write that stalls between the two, as one does when its
process is frozen, or when its file write is slow, lands under a name
that sorts ahead of every chunk other writers landed meanwhile:

- a live reader that already had those chunks gets the late one after
them;
- every read from index 0 gets it before them, so each of those chunks
moves one index on, and a reader that resumes from an index (`get(name,
startIndex)`, a `getChunks` cursor) repeats one chunk or misses one.

Two writers of one stream is what happens when a process stalls and
another takes its work over. libfx runs durable sessions on world-local
and numbers each line of a session's stream by its index, so a reader
that reconnects from a line's cursor depends on indexes never moving.
libfx's crash campaign on world-local hits this when it freezes a worker
with a stream write on its way: the frozen worker's line lands ahead of
lines the replacing worker wrote, which a reader in another process
already had, and nine later lines change index. The new test below
reproduces it without timing: held just before its rename, a chunk
minted before two other writers' chunks lands ahead of them.

This change names a chunk as it lands, in the new
`src/stream-chunk-place.ts`:

- **The name is the next one.** A chunk's name is the successor, in the
ULID alphabet, of the stream's newest chunk id, and the chunk is linked
into place with `link()`, which never replaces a file. The chunk lands
only if no other chunk has that name, so it lands after every chunk
already in the stream.
- **A writer that stalled after naming its chunk takes the next name.**
Its `link()` fails with `EEXIST` once another writer landed under that
name. It lists the stream and tries after the newest. No lock is needed,
and none would do: a lock held by a frozen process would hold every
writer of the stream until it went stale, and the frozen holder would
still land under its old name when it woke.
- **One streamer's writes land in the order they were called,** as they
did when each chunk was named then. Each write takes its turn in its
stream before any await, and lands only after the turns before it, so
the guarantee the promise `runId` test pins is kept. `writeMulti` still
writes its chunks in parallel and lands them in order.
- **No listing in the common case.** A streamer remembers the last chunk
it landed in each stream and tries the next name first. It lists the
stream for its first write to it and after a failed link.
- **A stream's first chunk is `chnk_0…01`,** the successor of the
tombstone a zero-retention purge leaves, which still sorts first.
- **A file system that makes no hard links** (`link()` fails with
`ENOTSUP`, `EOPNOTSUPP`, `ENOSYS`, `EXDEV`, or `EPERM` outside Windows)
renames the chunk into place as before, under a name minted as it lands.
That narrows the window from the whole write to the moment between
naming and renaming.

Signed-off-by: Pranit <pranit@vercel.com>
Co-authored-by: Peter Wielander <peter.wielander@vercel.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
Signed-off-by: Pranit <pranit@vercel.com>
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Backport PR opened against stable: #5188. Merge conflicts were resolved by AI — please review carefully. (backport job run)

VaguelySerious added a commit that referenced this pull request Nov 2, 2026
…lready in its stream (#5188)

Automated backport of #4909 to `stable` ([backport job
run](https://github.com/vercel/workflow/actions/runs/41509522981)).

Signed-off-by: Pranit <pranit@vercel.com>
Co-authored-by: Peter Wielander <peter.wielander@vercel.com>
Co-authored-by: Peter Wielander <mittgfu@gmail.com>
@github-actions github-actions Bot mentioned this pull request Nov 2, 2026
pranaygp added a commit that referenced this pull request Nov 3, 2026
### Description

Docs follow-ups for user-facing changes merged on Nov 2 that shipped
without docs on these pages:

- **CLI and Web UI**, **Vercel World**: `--project` (ID or slug) and
`--team` take precedence over the `.vercel` link, and in a monorepo the
CLI uses the nearest `.vercel/project.json` (#5182).
- **`createWebhook`**: outside Vercel, `url` uses `PORT`, then the
detected server port (#5184).
- **Sequential and parallel cookbook**: says `Promise.race` behaves as
in plain JavaScript.

#5187 is being reverted, so it isn't documented here. #5100 is a bug fix
that makes `Promise.race` behave as users expect, so it needs no docs
beyond the cookbook wording. #5183, #5189, #5190 and #4909 don't change
anything users do.

### Docs Preview

- [CLI and Web UI:
`--project`](https://workflow-docs-git-workflow-docs-cleanup-tue-3-nov.vercel.sh/docs/configuration/cli-and-web-ui#--project)
- [Vercel World:
Observability](https://workflow-docs-git-workflow-docs-cleanup-tue-3-nov.vercel.sh/worlds/vercel#observability)
-
[createWebhook](https://workflow-docs-git-workflow-docs-cleanup-tue-3-nov.vercel.sh/docs/api-reference/workflow/create-webhook)
- [Sequential and parallel: How it
works](https://workflow-docs-git-workflow-docs-cleanup-tue-3-nov.vercel.sh/cookbook/common-patterns/sequential-and-parallel#how-it-works)

### PR Checklist - Required to merge

- [x] 🔒 DCO sign-off included on every commit

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: Pranay Prakash <1797812+pranaygp@users.noreply.github.com>
pranaygp pushed a commit that referenced this pull request Nov 4, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to stable, this PR
will be updated.


# Releases
## @workflow/world-postgres@4.4.0

### Minor Changes

- [#4759](#4759)
[`3ff458e`](3ff458e)
Thanks [@pranaygp](https://github.com/pranaygp)! - Add
`jobLockStaleSeconds` (`WORKFLOW_POSTGRES_JOB_LOCK_STALE_SECONDS`,
default `0`, off). When set, a running delivery renews its Graphile job
lock, and a job whose process died is redelivered by another process
once its lock has gone unrenewed for that many seconds, instead of after
Graphile Worker's fixed 4 hours or the next restart. `30` is a good
value when more than one process shares the database. It's off by
default because of rolling upgrades: processes on an earlier version
don't renew their locks, so turn it on once every process that shares
the database runs this version.

### Patch Changes

- [#5136](#5136)
[`5c9b65d`](5c9b65d)
Thanks [@alangenfeld](https://github.com/alangenfeld)! - A `run_started`
that races the run ending no longer moves the run back to `running`;
it's refused with `RunExpiredError`.

- Updated dependencies
[[`ceaaa5c`](ceaaa5c),
[`b065b0b`](b065b0b)]:
  - @workflow/world-local@4.4.5
## @workflow/astro@4.0.31

### Patch Changes

- Updated dependencies
[[`e4dbc29`](e4dbc29)]:
  - @workflow/builders@4.1.21
  - @workflow/rollup@4.0.31
  - @workflow/vite@4.0.31
## @workflow/builders@4.1.21

### Patch Changes

- [#5063](#5063)
[`e4dbc29`](e4dbc29)
Thanks [@joelfickson](https://github.com/joelfickson)! - Export
`workflowSerdeComputedPropertyPattern` alongside the other workflow
pattern regexes.

- Updated dependencies []:
  - @workflow/core@4.8.16
## @workflow/cli@4.3.20

### Patch Changes

- [#5182](#5182)
[`f3d6142`](f3d6142)
Thanks [@VaguelySerious](https://github.com/VaguelySerious)! - Respect
`--project` and `--team` over the inferred `.vercel` link, and prefer
the nearest `.vercel/project.json` in monorepos

- Updated dependencies
[[`e4dbc29`](e4dbc29),
[`dbfd1ba`](dbfd1ba),
[`ceaaa5c`](ceaaa5c),
[`b065b0b`](b065b0b)]:
  - @workflow/builders@4.1.21
  - @workflow/web@4.1.32
  - @workflow/world-local@4.4.5
  - @workflow/core@4.8.16
## @workflow/core@4.8.16

### Patch Changes

- Updated dependencies
[[`ceaaa5c`](ceaaa5c),
[`b065b0b`](b065b0b)]:
  - @workflow/world-local@4.4.5
## @workflow/nest@4.0.32

### Patch Changes

- Updated dependencies
[[`e4dbc29`](e4dbc29)]:
  - @workflow/builders@4.1.21
## @workflow/next@4.1.20

### Patch Changes

- [#5063](#5063)
[`e4dbc29`](e4dbc29)
Thanks [@joelfickson](https://github.com/joelfickson)! - Run the
Turbopack workflow loader on files that use `static
[WORKFLOW_SERIALIZE]` computed keys (such as `@ai-sdk/openai` models),
fixing "Class … not found" errors in steps.

- Updated dependencies
[[`e4dbc29`](e4dbc29)]:
  - @workflow/builders@4.1.21
  - @workflow/core@4.8.16
## @workflow/nitro@4.1.22

### Patch Changes

- Updated dependencies
[[`e4dbc29`](e4dbc29),
[`dbfd1ba`](dbfd1ba)]:
  - @workflow/builders@4.1.21
  - @workflow/web@4.1.32
  - @workflow/rollup@4.0.31
  - @workflow/vite@4.0.31
  - @workflow/core@4.8.16
## @workflow/nuxt@4.0.32

### Patch Changes

- [#5211](#5211)
[`d30f07e`](d30f07e)
Thanks [@atinux](https://github.com/atinux)! - Upgrade `@nuxt/kit` to
4.6.3 to pick up the fix for CVE-2026-71314.

- Updated dependencies []:
  - @workflow/nitro@4.1.22
## @workflow/rollup@4.0.31

### Patch Changes

- Updated dependencies
[[`e4dbc29`](e4dbc29)]:
  - @workflow/builders@4.1.21
## @workflow/sveltekit@4.0.31

### Patch Changes

- Updated dependencies
[[`e4dbc29`](e4dbc29)]:
  - @workflow/builders@4.1.21
  - @workflow/rollup@4.0.31
  - @workflow/vite@4.0.31
## @workflow/vite@4.0.31

### Patch Changes

- Updated dependencies
[[`e4dbc29`](e4dbc29)]:
  - @workflow/builders@4.1.21
## @workflow/vitest@4.0.32

### Patch Changes

- Updated dependencies
[[`e4dbc29`](e4dbc29),
[`ceaaa5c`](ceaaa5c),
[`b065b0b`](b065b0b)]:
  - @workflow/builders@4.1.21
  - @workflow/world-local@4.4.5
  - @workflow/rollup@4.0.31
  - @workflow/core@4.8.16
## @workflow/web@4.1.32

### Patch Changes

- [#5079](#5079)
[`dbfd1ba`](dbfd1ba)
Thanks [@karthikscale3](https://github.com/karthikscale3)! - Recognize
unframed serialized abort packets in the Streams viewer so encrypted
packets can be decrypted.
## @workflow/web-shared@4.1.32

### Patch Changes

- Updated dependencies []:
  - @workflow/core@4.8.16
## workflow@4.8.16

### Patch Changes

- Updated dependencies
[[`f3d6142`](f3d6142),
[`d30f07e`](d30f07e),
[`e4dbc29`](e4dbc29)]:
  - @workflow/cli@4.3.20
  - @workflow/nuxt@4.0.32
  - @workflow/next@4.1.20
  - @workflow/astro@4.0.31
  - @workflow/nest@4.0.32
  - @workflow/nitro@4.1.22
  - @workflow/rollup@4.0.31
  - @workflow/sveltekit@4.0.31
  - @workflow/core@4.8.16
  - @workflow/typescript-plugin@4.0.3
## @workflow/world-local@4.4.5

### Patch Changes

- [#5069](#5069)
[`ceaaa5c`](ceaaa5c)
Thanks [@AndrewBarba](https://github.com/AndrewBarba)! - Deliver stream
chunks in the order they were written. A reader that opened, or polled,
while chunks were being written could get a later chunk before an
earlier one, and could get a chunk twice. Writes to one stream now land
in order, including a `writeToStreamMulti` group and concurrent writes,
and the reader holds live chunks while it reads from disk.

- [#4909](#4909)
[`b065b0b`](b065b0b)
Thanks [@fazxes](https://github.com/fazxes)! - Name each stream chunk as
it lands, after every chunk already in the stream, so a write that
stalled before landing no longer lands ahead of chunks a reader already
read.
## @workflow/world-testing@4.1.31

### Patch Changes

- Updated dependencies
[[`f3d6142`](f3d6142)]:
  - @workflow/cli@4.3.20
  - workflow@4.8.16
  - @workflow/core@4.8.16

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants