From 05c22c77ec96baecbe940577a0f62d9b164249fd Mon Sep 17 00:00:00 2001 From: Tim Smart Date: Sat, 12 Sep 2026 08:58:55 +0000 Subject: [PATCH 1/7] test: reproduce multipart hang on active file body failure --- .../test/unstable/http/Multipart.test.ts | 27 ++++++++++ .../platform/bun/test/BunMultipart.test.ts | 50 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 packages/platform/bun/test/BunMultipart.test.ts diff --git a/packages/effect/test/unstable/http/Multipart.test.ts b/packages/effect/test/unstable/http/Multipart.test.ts index 8c764aa578a..e88d003148f 100644 --- a/packages/effect/test/unstable/http/Multipart.test.ts +++ b/packages/effect/test/unstable/http/Multipart.test.ts @@ -240,6 +240,33 @@ describe("Multipart", () => { strictEqual(error.reason._tag, "BodyTooLarge") })) + it.live("propagates upstream failure while consuming an active file", () => + Effect.gen(function*() { + const upstreamError = Multipart.MultipartError.fromReason("InternalError", new Error("body-read-failed")) + const fileStart = new TextEncoder().encode( + "--b\r\nContent-Disposition: form-data; name=\"file\"; filename=\"a.txt\"\r\n\r\nhello" + ) + let bytesRead = 0 + + const error = yield* Stream.make(fileStart).pipe( + Stream.concat(Stream.fail(upstreamError)), + Stream.pipeThroughChannel(Multipart.makeChannel({ "content-type": "multipart/form-data; boundary=b" })), + Stream.runForEach((part) => + part._tag === "File" + ? Stream.runForEach(part.content, (chunk) => + Effect.sync(() => { + bytesRead += chunk.length + })) + : Effect.die("expected file") + ), + Effect.timeout("1 second"), + Effect.flip + ) + + strictEqual(bytesRead, 5) + strictEqual(error, upstreamError) + })) + it.effect("propagates Parse when the body ends mid-file", () => Effect.gen(function*() { const boundary = "----testboundary" diff --git a/packages/platform/bun/test/BunMultipart.test.ts b/packages/platform/bun/test/BunMultipart.test.ts new file mode 100644 index 00000000000..1088a9047d1 --- /dev/null +++ b/packages/platform/bun/test/BunMultipart.test.ts @@ -0,0 +1,50 @@ +import * as BunMultipart from "@effect/platform-bun/BunMultipart" +import { describe, it } from "@effect/vitest" +import { Effect, Stream } from "effect" +import { strictEqual } from "node:assert" + +describe("BunMultipart", () => { + it.live("propagates a request body read error while consuming an active file", () => + Effect.gen(function*() { + const cause = new Error("body-read-failed") + const body = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode( + "--b\r\nContent-Disposition: form-data; name=\"file\"; filename=\"a.txt\"\r\n\r\nhello" + )) + }, + pull(controller) { + controller.error(cause) + } + }, { + // Keep the error on the next pull so the file bytes are consumed first. + highWaterMark: 0 + }) + const request = new Request("http://localhost/upload", { + method: "POST", + headers: { "content-type": "multipart/form-data; boundary=b" }, + body + }) + let bytesRead = 0 + + const error = yield* BunMultipart.stream(request).pipe( + Stream.runForEach((part) => + part._tag === "File" + ? Stream.runForEach(part.content, (chunk) => + Effect.sync(() => { + bytesRead += chunk.length + })) + : Effect.die("expected file") + ), + Effect.timeout("1 second"), + Effect.flip + ) + + strictEqual(bytesRead, 5) + strictEqual(error._tag, "MultipartError") + if (error._tag === "MultipartError") { + strictEqual(error.reason._tag, "InternalError") + strictEqual(error.reason.cause, cause) + } + })) +}) From f0f08a2a3e03c1c8449c8118dc869e1939283854 Mon Sep 17 00:00:00 2001 From: Tim Smart Date: Sat, 12 Sep 2026 09:01:51 +0000 Subject: [PATCH 2/7] fix: propagate multipart failures to active file consumers --- .../pre/fix-multipart-active-file-failure.md | 5 +++ .../effect/src/unstable/http/Multipart.ts | 37 ++++++++++++------- 2 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 .changeset/pre/fix-multipart-active-file-failure.md diff --git a/.changeset/pre/fix-multipart-active-file-failure.md b/.changeset/pre/fix-multipart-active-file-failure.md new file mode 100644 index 00000000000..475b4f636fe --- /dev/null +++ b/.changeset/pre/fix-multipart-active-file-failure.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Propagate upstream body failures to active multipart file consumers instead of hanging after buffered file bytes are consumed. Preserve existing multipart errors and wrap other upstream errors as internal multipart errors. diff --git a/packages/effect/src/unstable/http/Multipart.ts b/packages/effect/src/unstable/http/Multipart.ts index f60f4aceaff..af472c8566b 100644 --- a/packages/effect/src/unstable/http/Multipart.ts +++ b/packages/effect/src/unstable/http/Multipart.ts @@ -480,14 +480,21 @@ export const makeChannel = (headers: Record): Channel.Channe let chunks: Array = [] let finished = false const pullChunks = Channel.fromPull( - Effect.succeed(Effect.suspend(function loop(): Pull.Pull> { - if (!Arr.isReadonlyArrayNonEmpty(chunks)) { - return finished ? Cause.done() : Effect.flatMap(pump, loop) - } - const chunk = chunks - chunks = [] - return Effect.succeed(chunk) - })) + Effect.succeed( + Effect.suspend(function loop(): Pull.Pull, IE | MultipartError> { + if (!Arr.isReadonlyArrayNonEmpty(chunks)) { + if (finished) { + return Cause.done() + } else if (Option.isSome(exit)) { + return exit.value + } + return Effect.flatMap(pump, loop) + } + const chunk = chunks + chunks = [] + return Effect.succeed(chunk) + }) + ) ) partsBuffer.push(new FileImpl(info, pullChunks)) return function(chunk) { @@ -614,17 +621,21 @@ class FileImpl extends PartBase implements File { constructor( info: MP.PartInfo, - channel: Channel.Channel> + channel: Channel.Channel, unknown> ) { super() this.key = info.name this.name = info.filename ?? info.name this.contentType = info.contentType - this.content = Stream.fromChannel(channel) - this.contentEffect = channel.pipe( - Channel.mkUint8Array, - Effect.mapError((cause) => MultipartError.fromReason("InternalError", cause)) + const content = Channel.mapError( + channel, + (cause) => + Predicate.hasProperty(cause, MultipartErrorTypeId) + ? cause as MultipartError + : MultipartError.fromReason("InternalError", cause) ) + this.content = Stream.fromChannel(content) + this.contentEffect = Channel.mkUint8Array(content) } toJSON(): unknown { From f23ade52a507f4021edaaa7ba078987c238db4be Mon Sep 17 00:00:00 2001 From: Tim Smart Date: Sat, 12 Sep 2026 09:15:20 +0000 Subject: [PATCH 3/7] test: cover multipart persisted and contentEffect failures --- .../test/unstable/http/Multipart.test.ts | 70 ++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/packages/effect/test/unstable/http/Multipart.test.ts b/packages/effect/test/unstable/http/Multipart.test.ts index e88d003148f..ac6368c425d 100644 --- a/packages/effect/test/unstable/http/Multipart.test.ts +++ b/packages/effect/test/unstable/http/Multipart.test.ts @@ -1,5 +1,5 @@ import { describe, it } from "@effect/vitest" -import { ByteSize, Effect, ErrorReporter, FileSystem, identity, Path, Schema, Stream, Unify } from "effect" +import { ByteSize, Effect, ErrorReporter, FileSystem, identity, Path, Schema, Sink, Stream, Unify } from "effect" import { HttpClientRequest, HttpIncomingMessage, @@ -267,6 +267,74 @@ describe("Multipart", () => { strictEqual(error, upstreamError) })) + it.live("preserves upstream failure when collecting an active file with contentEffect", () => + Effect.gen(function*() { + const upstreamError = Multipart.MultipartError.fromReason("InternalError", new Error("body-read-failed")) + let contentError: Multipart.MultipartError | undefined + const fileStart = new TextEncoder().encode( + "--b\r\nContent-Disposition: form-data; name=\"file\"; filename=\"a.txt\"\r\n\r\nhello" + ) + + const error = yield* Stream.make(fileStart).pipe( + Stream.concat(Stream.fail(upstreamError)), + Stream.pipeThroughChannel(Multipart.makeChannel({ "content-type": "multipart/form-data; boundary=b" })), + Stream.runForEach((part) => + part._tag === "File" + ? part.contentEffect.pipe(Effect.tapError((error) => + Effect.sync(() => { + contentError = error + }) + )) + : Effect.die("expected file") + ), + Effect.timeout("1 second"), + Effect.flip + ) + + strictEqual(contentError, upstreamError) + strictEqual(error, upstreamError) + })) + + it.live("preserves the upstream error cause when persisting an active file", () => + Effect.gen(function*() { + const cause = new Error("body-read-failed") + const upstreamError = Multipart.MultipartError.fromReason("InternalError", cause) + const fileStart = new TextEncoder().encode( + "--b\r\nContent-Disposition: form-data; name=\"file\"; filename=\"a.txt\"\r\n\r\nhello" + ) + let bytesWritten = 0 + + const error = yield* Stream.make(fileStart).pipe( + Stream.concat(Stream.fail(upstreamError)), + Stream.pipeThroughChannel(Multipart.makeChannel({ "content-type": "multipart/form-data; boundary=b" })), + Multipart.toPersisted, + Effect.provideService( + FileSystem.FileSystem, + FileSystem.makeNoop({ + makeTempDirectoryScoped: () => Effect.succeed("/tmp/multipart-test"), + sink: () => + Sink.forEach((chunk: Uint8Array) => + Effect.sync(() => { + bytesWritten += chunk.length + }) + ) + }) + ), + Effect.provide(Path.layer), + Effect.scoped, + Effect.timeout("1 second"), + Effect.flip + ) + + strictEqual(bytesWritten, 5) + strictEqual(error._tag, "MultipartError") + if (error._tag === "MultipartError") { + strictEqual(error.reason._tag, "InternalError") + strictEqual(error.reason.cause, cause) + strictEqual(error, upstreamError) + } + })) + it.effect("propagates Parse when the body ends mid-file", () => Effect.gen(function*() { const boundary = "----testboundary" From 9393362ead1787f8eed50d90bc3045a295df96e8 Mon Sep 17 00:00:00 2001 From: Tim Smart Date: Sat, 12 Sep 2026 09:17:32 +0000 Subject: [PATCH 4/7] fix: preserve multipart errors during file persistence --- .../{pre => }/fix-multipart-active-file-failure.md | 0 packages/effect/src/unstable/http/Multipart.ts | 9 ++++----- 2 files changed, 4 insertions(+), 5 deletions(-) rename .changeset/{pre => }/fix-multipart-active-file-failure.md (100%) diff --git a/.changeset/pre/fix-multipart-active-file-failure.md b/.changeset/fix-multipart-active-file-failure.md similarity index 100% rename from .changeset/pre/fix-multipart-active-file-failure.md rename to .changeset/fix-multipart-active-file-failure.md diff --git a/packages/effect/src/unstable/http/Multipart.ts b/packages/effect/src/unstable/http/Multipart.ts index af472c8566b..7f0bc8c865b 100644 --- a/packages/effect/src/unstable/http/Multipart.ts +++ b/packages/effect/src/unstable/http/Multipart.ts @@ -199,6 +199,8 @@ export interface Persisted { const MultipartErrorTypeId = "~effect/http/Multipart/MultipartError" +const isMultipartError = (u: unknown): u is MultipartError => Predicate.hasProperty(u, MultipartErrorTypeId) + /** * Error reason carried by a `MultipartError`. * @@ -629,10 +631,7 @@ class FileImpl extends PartBase implements File { this.contentType = info.contentType const content = Channel.mapError( channel, - (cause) => - Predicate.hasProperty(cause, MultipartErrorTypeId) - ? cause as MultipartError - : MultipartError.fromReason("InternalError", cause) + (cause) => isMultipartError(cause) ? cause : MultipartError.fromReason("InternalError", cause) ) this.content = Stream.fromChannel(content) this.contentEffect = Channel.mkUint8Array(content) @@ -655,7 +654,7 @@ const defaultWriteFile = (path: string, file: File) => (fs) => Effect.mapError( Stream.run(file.content, fs.sink(path)), - (cause) => MultipartError.fromReason("InternalError", cause) + (cause) => isMultipartError(cause) ? cause : MultipartError.fromReason("InternalError", cause) ) ) From 555c2effb2314ad9149b6b1348faf689cf11f742 Mon Sep 17 00:00:00 2001 From: Tim Smart Date: Sat, 12 Sep 2026 11:45:02 +0000 Subject: [PATCH 5/7] refactor: simplify multipart failure propagation Name the MultipartError normalisation once, map upstream errors where IE is in scope so FileImpl stays a plain holder, flatten the file pull loop guards, and share the failing-body fixture across the regression tests. --- .../effect/src/unstable/http/Multipart.ts | 26 ++++----- .../test/unstable/http/Multipart.test.ts | 54 ++++++------------- .../platform/bun/test/BunMultipart.test.ts | 10 ++-- 3 files changed, 32 insertions(+), 58 deletions(-) diff --git a/packages/effect/src/unstable/http/Multipart.ts b/packages/effect/src/unstable/http/Multipart.ts index 7f0bc8c865b..9735b07fdba 100644 --- a/packages/effect/src/unstable/http/Multipart.ts +++ b/packages/effect/src/unstable/http/Multipart.ts @@ -199,7 +199,10 @@ export interface Persisted { const MultipartErrorTypeId = "~effect/http/Multipart/MultipartError" -const isMultipartError = (u: unknown): u is MultipartError => Predicate.hasProperty(u, MultipartErrorTypeId) +const toMultipartError = (cause: unknown): MultipartError => + Predicate.hasProperty(cause, MultipartErrorTypeId) + ? cause as MultipartError + : MultipartError.fromReason("InternalError", cause) /** * Error reason carried by a `MultipartError`. @@ -487,7 +490,8 @@ export const makeChannel = (headers: Record): Channel.Channe if (!Arr.isReadonlyArrayNonEmpty(chunks)) { if (finished) { return Cause.done() - } else if (Option.isSome(exit)) { + } + if (Option.isSome(exit)) { return exit.value } return Effect.flatMap(pump, loop) @@ -498,7 +502,7 @@ export const makeChannel = (headers: Record): Channel.Channe }) ) ) - partsBuffer.push(new FileImpl(info, pullChunks)) + partsBuffer.push(new FileImpl(info, Channel.mapError(pullChunks, toMultipartError))) return function(chunk) { if (chunk === null) { finished = true @@ -623,18 +627,14 @@ class FileImpl extends PartBase implements File { constructor( info: MP.PartInfo, - channel: Channel.Channel, unknown> + channel: Channel.Channel, MultipartError> ) { super() this.key = info.name this.name = info.filename ?? info.name this.contentType = info.contentType - const content = Channel.mapError( - channel, - (cause) => isMultipartError(cause) ? cause : MultipartError.fromReason("InternalError", cause) - ) - this.content = Stream.fromChannel(content) - this.contentEffect = Channel.mkUint8Array(content) + this.content = Stream.fromChannel(channel) + this.contentEffect = Channel.mkUint8Array(channel) } toJSON(): unknown { @@ -651,11 +651,7 @@ class FileImpl extends PartBase implements File { const defaultWriteFile = (path: string, file: File) => Effect.flatMap( FileSystem.FileSystem, - (fs) => - Effect.mapError( - Stream.run(file.content, fs.sink(path)), - (cause) => isMultipartError(cause) ? cause : MultipartError.fromReason("InternalError", cause) - ) + (fs) => Effect.mapError(Stream.run(file.content, fs.sink(path)), toMultipartError) ) /** diff --git a/packages/effect/test/unstable/http/Multipart.test.ts b/packages/effect/test/unstable/http/Multipart.test.ts index ac6368c425d..ca5a2ba9114 100644 --- a/packages/effect/test/unstable/http/Multipart.test.ts +++ b/packages/effect/test/unstable/http/Multipart.test.ts @@ -240,17 +240,23 @@ describe("Multipart", () => { strictEqual(error.reason._tag, "BodyTooLarge") })) + // A file part whose header and first five bytes arrive, then the upstream body fails. + const activeFileParts = (error: Multipart.MultipartError) => + Stream.make( + new TextEncoder().encode( + "--b\r\nContent-Disposition: form-data; name=\"file\"; filename=\"a.txt\"\r\n\r\nhello" + ) + ).pipe( + Stream.concat(Stream.fail(error)), + Stream.pipeThroughChannel(Multipart.makeChannel({ "content-type": "multipart/form-data; boundary=b" })) + ) + it.live("propagates upstream failure while consuming an active file", () => Effect.gen(function*() { const upstreamError = Multipart.MultipartError.fromReason("InternalError", new Error("body-read-failed")) - const fileStart = new TextEncoder().encode( - "--b\r\nContent-Disposition: form-data; name=\"file\"; filename=\"a.txt\"\r\n\r\nhello" - ) let bytesRead = 0 - const error = yield* Stream.make(fileStart).pipe( - Stream.concat(Stream.fail(upstreamError)), - Stream.pipeThroughChannel(Multipart.makeChannel({ "content-type": "multipart/form-data; boundary=b" })), + const error = yield* activeFileParts(upstreamError).pipe( Stream.runForEach((part) => part._tag === "File" ? Stream.runForEach(part.content, (chunk) => @@ -270,43 +276,22 @@ describe("Multipart", () => { it.live("preserves upstream failure when collecting an active file with contentEffect", () => Effect.gen(function*() { const upstreamError = Multipart.MultipartError.fromReason("InternalError", new Error("body-read-failed")) - let contentError: Multipart.MultipartError | undefined - const fileStart = new TextEncoder().encode( - "--b\r\nContent-Disposition: form-data; name=\"file\"; filename=\"a.txt\"\r\n\r\nhello" - ) - const error = yield* Stream.make(fileStart).pipe( - Stream.concat(Stream.fail(upstreamError)), - Stream.pipeThroughChannel(Multipart.makeChannel({ "content-type": "multipart/form-data; boundary=b" })), - Stream.runForEach((part) => - part._tag === "File" - ? part.contentEffect.pipe(Effect.tapError((error) => - Effect.sync(() => { - contentError = error - }) - )) - : Effect.die("expected file") - ), + const error = yield* activeFileParts(upstreamError).pipe( + Stream.runForEach((part) => part._tag === "File" ? part.contentEffect : Effect.die("expected file")), Effect.timeout("1 second"), Effect.flip ) - strictEqual(contentError, upstreamError) strictEqual(error, upstreamError) })) it.live("preserves the upstream error cause when persisting an active file", () => Effect.gen(function*() { - const cause = new Error("body-read-failed") - const upstreamError = Multipart.MultipartError.fromReason("InternalError", cause) - const fileStart = new TextEncoder().encode( - "--b\r\nContent-Disposition: form-data; name=\"file\"; filename=\"a.txt\"\r\n\r\nhello" - ) + const upstreamError = Multipart.MultipartError.fromReason("InternalError", new Error("body-read-failed")) let bytesWritten = 0 - const error = yield* Stream.make(fileStart).pipe( - Stream.concat(Stream.fail(upstreamError)), - Stream.pipeThroughChannel(Multipart.makeChannel({ "content-type": "multipart/form-data; boundary=b" })), + const error = yield* activeFileParts(upstreamError).pipe( Multipart.toPersisted, Effect.provideService( FileSystem.FileSystem, @@ -327,12 +312,7 @@ describe("Multipart", () => { ) strictEqual(bytesWritten, 5) - strictEqual(error._tag, "MultipartError") - if (error._tag === "MultipartError") { - strictEqual(error.reason._tag, "InternalError") - strictEqual(error.reason.cause, cause) - strictEqual(error, upstreamError) - } + strictEqual(error, upstreamError) })) it.effect("propagates Parse when the body ends mid-file", () => diff --git a/packages/platform/bun/test/BunMultipart.test.ts b/packages/platform/bun/test/BunMultipart.test.ts index 1088a9047d1..e5b81a977bb 100644 --- a/packages/platform/bun/test/BunMultipart.test.ts +++ b/packages/platform/bun/test/BunMultipart.test.ts @@ -1,7 +1,7 @@ import * as BunMultipart from "@effect/platform-bun/BunMultipart" import { describe, it } from "@effect/vitest" +import { assertTrue, strictEqual } from "@effect/vitest/utils" import { Effect, Stream } from "effect" -import { strictEqual } from "node:assert" describe("BunMultipart", () => { it.live("propagates a request body read error while consuming an active file", () => @@ -41,10 +41,8 @@ describe("BunMultipart", () => { ) strictEqual(bytesRead, 5) - strictEqual(error._tag, "MultipartError") - if (error._tag === "MultipartError") { - strictEqual(error.reason._tag, "InternalError") - strictEqual(error.reason.cause, cause) - } + assertTrue(error._tag === "MultipartError") + strictEqual(error.reason._tag, "InternalError") + strictEqual(error.reason.cause, cause) })) }) From fd0661dc4f73d8c6b1b9c72762221e095a289b78 Mon Sep 17 00:00:00 2001 From: Tim Smart Date: Sat, 12 Sep 2026 11:53:11 +0000 Subject: [PATCH 6/7] test: reject silent multipart file completion --- packages/effect/test/unstable/http/Multipart.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/effect/test/unstable/http/Multipart.test.ts b/packages/effect/test/unstable/http/Multipart.test.ts index ca5a2ba9114..0ca73877b21 100644 --- a/packages/effect/test/unstable/http/Multipart.test.ts +++ b/packages/effect/test/unstable/http/Multipart.test.ts @@ -262,7 +262,7 @@ describe("Multipart", () => { ? Stream.runForEach(part.content, (chunk) => Effect.sync(() => { bytesRead += chunk.length - })) + })).pipe(Effect.flatMap(() => Effect.die("content should have failed"))) : Effect.die("expected file") ), Effect.timeout("1 second"), @@ -278,7 +278,11 @@ describe("Multipart", () => { const upstreamError = Multipart.MultipartError.fromReason("InternalError", new Error("body-read-failed")) const error = yield* activeFileParts(upstreamError).pipe( - Stream.runForEach((part) => part._tag === "File" ? part.contentEffect : Effect.die("expected file")), + Stream.runForEach((part) => + part._tag === "File" + ? Effect.flatMap(part.contentEffect, () => Effect.die("contentEffect should have failed")) + : Effect.die("expected file") + ), Effect.timeout("1 second"), Effect.flip ) From 39c0113da327d2d268c567c7ace2866208db7c9a Mon Sep 17 00:00:00 2001 From: Tim Smart Date: Sat, 12 Sep 2026 11:59:34 +0000 Subject: [PATCH 7/7] docs: clarify multipart changeset and test comments --- .changeset/fix-multipart-active-file-failure.md | 2 +- packages/effect/test/unstable/http/Multipart.test.ts | 1 - packages/platform/bun/test/BunMultipart.test.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.changeset/fix-multipart-active-file-failure.md b/.changeset/fix-multipart-active-file-failure.md index 475b4f636fe..bff8687e187 100644 --- a/.changeset/fix-multipart-active-file-failure.md +++ b/.changeset/fix-multipart-active-file-failure.md @@ -2,4 +2,4 @@ "effect": patch --- -Propagate upstream body failures to active multipart file consumers instead of hanging after buffered file bytes are consumed. Preserve existing multipart errors and wrap other upstream errors as internal multipart errors. +Fix multipart file streams hanging on body read errors and preserve error causes when persisting files. diff --git a/packages/effect/test/unstable/http/Multipart.test.ts b/packages/effect/test/unstable/http/Multipart.test.ts index 0ca73877b21..b8965d12054 100644 --- a/packages/effect/test/unstable/http/Multipart.test.ts +++ b/packages/effect/test/unstable/http/Multipart.test.ts @@ -240,7 +240,6 @@ describe("Multipart", () => { strictEqual(error.reason._tag, "BodyTooLarge") })) - // A file part whose header and first five bytes arrive, then the upstream body fails. const activeFileParts = (error: Multipart.MultipartError) => Stream.make( new TextEncoder().encode( diff --git a/packages/platform/bun/test/BunMultipart.test.ts b/packages/platform/bun/test/BunMultipart.test.ts index e5b81a977bb..6519381d9c6 100644 --- a/packages/platform/bun/test/BunMultipart.test.ts +++ b/packages/platform/bun/test/BunMultipart.test.ts @@ -17,7 +17,7 @@ describe("BunMultipart", () => { controller.error(cause) } }, { - // Keep the error on the next pull so the file bytes are consumed first. + // Delay the error until the file bytes are consumed. highWaterMark: 0 }) const request = new Request("http://localhost/upload", {