Version
v25.2.1
Platform
Linux 6.17.9-arch1-1 #1 SMP PREEMPT_DYNAMIC Mon, 24 Nov 2025 15:21:09 +0000 x86_64 GNU/Linux
Subsystem
stream
What steps will reproduce the bug?
Create a duplex pair and destroy one side with an error.
import stream from "node:stream";
const [ sideA, sideB ] = stream.duplexPair();
sideA.on("error", (err) => {
console.error("sideA error:", err);
});
sideA.on("close", () => {
console.log("sideA closed");
});
sideA.resume();
sideB.on("error", (err) => {
console.error("sideB error:", err);
});
sideB.on("close", () => {
console.log("sideB closed");
});
sideB.destroy(Error("Simulated error on sideB"));
How often does it reproduce? Is there a required condition?
Always
What is the expected behavior? Why is that the expected behavior?
I would expect on of the following outputs.
Ideally:
sideB error: Error: Simulated error on sideB
at file:///duplex-pair-test.ts:23:15
at ModuleJob.run (node:internal/modules/esm/module_job:413:25)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:654:26)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
sideB closed
sideA error: Error: Simulated error on sideB
at file:///duplex-pair-test.ts:23:15
at ModuleJob.run (node:internal/modules/esm/module_job:413:25)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:654:26)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
sideA closed
or
sideB error: Error: Simulated error on sideB
at file:///duplex-pair-test.ts:23:15
at ModuleJob.run (node:internal/modules/esm/module_job:413:25)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:654:26)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
sideB closed
sideA closed
The latter would be what I see, if I created a TCP socket pair manually.
What do you see instead?
sideB error: Error: Simulated error on sideB
at file:///home/simon/projects/krevp/lib/new/duplex-pair-test.ts:23:15
at ModuleJob.run (node:internal/modules/esm/module_job:413:25)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:654:26)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
sideB closed
sideA doesn't notice that sideB has errored or closed.
Additional information
In the provided minimal example the process also closes, as there are no pending tasks. If I keep it open via e.g. setInterval, still, no events on sideA.
Version
v25.2.1
Platform
Subsystem
stream
What steps will reproduce the bug?
Create a duplex pair and destroy one side with an error.
How often does it reproduce? Is there a required condition?
Always
What is the expected behavior? Why is that the expected behavior?
I would expect on of the following outputs.
Ideally:
or
The latter would be what I see, if I created a TCP socket pair manually.
What do you see instead?
sideA doesn't notice that sideB has errored or closed.
Additional information
In the provided minimal example the process also closes, as there are no pending tasks. If I keep it open via e.g. setInterval, still, no events on sideA.