Version
Platform
Darwin MacBook-Air.local 25.3.0 Darwin Kernel Version 25.3.0: Wed Jan 28 20:49:24 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T8132 arm64
Subsystem
stream/web (TransformStream)
What steps will reproduce the bug?
Run:
import { TransformStream } from 'node:stream/web';
import { setTimeout } from 'node:timers/promises';
const stream = new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk);
},
});
// Important for reproducing this race consistently in my environment.
await setTimeout(500);
const reader = stream.readable.getReader();
const writer = stream.writable.getWriter();
// Release backpressure.
const pendingRead = reader.read();
// Simulate client disconnect / shutdown.
const pendingCancel = reader.cancel(new Error('client disconnected'));
// Late write racing with cancel.
const pendingLateWrite = writer.write('late-write');
const results = await Promise.allSettled([
pendingRead,
pendingCancel,
pendingLateWrite,
]);
console.log(results);
How often does it reproduce? Is there a required condition?
Intermittent race. In my environment, adding await setTimeout(...) before acquiring reader/writer is required for reliable reproduction.
What is the expected behavior? Why is that the expected behavior?
No internal implementation TypeError should leak.
My understanding is that, when shutdown starts concurrently (cancel/abort/close paths), pending or in-flight write() operations are expected to settle coherently with stream shutdown semantics (resolve/reject according to stream state), rather than failing because an internal algorithm slot became non-callable.
What do you see instead?
pendingLateWrite can reject with an internal error:
TypeError: controller[kState].transformAlgorithm is not a function
at transformStreamDefaultControllerPerformTransform (node:internal/webstreams/transformstream:527:37)
at transformStreamDefaultSinkWriteAlgorithm (node:internal/webstreams/transformstream:573:10)
...
Additional information
This looks like a race where:
transformStreamDefaultSinkWriteAlgorithm has accepted/scheduled a write,
- shutdown starts (
readable.cancel() / writable.abort() / close path),
transformStreamDefaultControllerClearAlgorithms clears transformAlgorithm,
- pending write continues and reaches
transformStreamDefaultControllerPerformTransform.
Related discussions:
Version
Platform
Subsystem
stream/web (TransformStream)
What steps will reproduce the bug?
Run:
How often does it reproduce? Is there a required condition?
Intermittent race. In my environment, adding
await setTimeout(...)before acquiring reader/writer is required for reliable reproduction.What is the expected behavior? Why is that the expected behavior?
No internal implementation TypeError should leak.
My understanding is that, when shutdown starts concurrently (cancel/abort/close paths), pending or in-flight
write()operations are expected to settle coherently with stream shutdown semantics (resolve/reject according to stream state), rather than failing because an internal algorithm slot became non-callable.What do you see instead?
pendingLateWritecan reject with an internal error:TypeError: controller[kState].transformAlgorithm is not a function at transformStreamDefaultControllerPerformTransform (node:internal/webstreams/transformstream:527:37) at transformStreamDefaultSinkWriteAlgorithm (node:internal/webstreams/transformstream:573:10) ...Additional information
This looks like a race where:
transformStreamDefaultSinkWriteAlgorithmhas accepted/scheduled a write,readable.cancel()/writable.abort()/ close path),transformStreamDefaultControllerClearAlgorithmsclearstransformAlgorithm,transformStreamDefaultControllerPerformTransform.Related discussions:
transformAlgorithm is not a functionsourcebot-dev/sourcebot#170