fix(ssh-tunnel): ignore stderr stream errors during teardown (#100855)

* fix(ssh-tunnel): ignore stderr stream errors during teardown

* chore(proof): add real behavior proof for ssh-tunnel stderr stream errors

* chore(proof): fix promise executor lint in ssh-tunnel proof

* test(ssh-tunnel): keep stream error proof in regression suite

* test(ssh): cover active stream errors

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
cxbAsDev
2026-07-07 00:03:29 +08:00
committed by GitHub
parent d9bce26fd7
commit 32cc9b08e6
2 changed files with 31 additions and 2 deletions

View File

@@ -205,4 +205,29 @@ describe("startSshPortForward", () => {
await rejection;
expect(kill).toHaveBeenCalledWith("SIGTERM");
});
it.each(["active", "teardown"] as const)(
"does not crash when stderr errors while the tunnel is %s",
async (phase) => {
vi.useFakeTimers();
spawnFakeSshListening();
const tunnel = await startSshPortForward({
target: "me@example.com:2222",
localPortPreferred: 43210,
remotePort: 18789,
timeoutMs: 1000,
});
const child = mocks.spawn.mock.results[0]?.value as EventEmitter & {
killed: boolean;
stderr: EventEmitter;
};
const stopping = phase === "teardown" ? tunnel.stop() : undefined;
expect(child.killed).toBe(phase === "teardown");
expect(() => child.stderr.emit("error", new Error("stderr EPIPE"))).not.toThrow();
await expect(stopping ?? tunnel.stop()).resolves.toBeUndefined();
},
);
});

View File

@@ -165,8 +165,12 @@ export async function startSshPortForward(opts: {
const child = spawn("/usr/bin/ssh", args, {
stdio: ["ignore", "ignore", "pipe"],
});
child.stderr?.setEncoding("utf8");
child.stderr?.on("data", (chunk) => {
const stderrStream = child.stderr;
// Child events own tunnel failure. Keep the diagnostic pipe observed so a
// stream error cannot become an uncaught exception during active use or teardown.
stderrStream?.on("error", () => {});
stderrStream?.setEncoding("utf8");
stderrStream?.on("data", (chunk) => {
const lines = normalizeStringEntries(String(chunk).split("\n"));
stderr.push(...lines);
});