From 3bb7b37df0362f9e9e831b6e1049be5d3cdb22d4 Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Tue, 1 Sep 2026 20:25:34 -0300 Subject: [PATCH] test(config): stop a broken pipe preempting the exit-code diagnosis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `git diff-tree --stdin` given a cwd outside any repository exits before it reads a hash, so the write that follows can land on a dead process and raise `EPIPE: broken pipe, send` instead of the exit-code error the caller reports. Which one surfaces is a race against process startup — green on an idle machine, red on a loaded CI runner. The exit code and stderr are the diagnosis, so a broken pipe on stdin is dropped and the reporting left to them. --- .../scripts/semantic-release-path-filter.ts | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/config/scripts/semantic-release-path-filter.ts b/packages/config/scripts/semantic-release-path-filter.ts index e7efd7a0c4..a31306c7df 100644 --- a/packages/config/scripts/semantic-release-path-filter.ts +++ b/packages/config/scripts/semantic-release-path-filter.ts @@ -50,6 +50,19 @@ const releaseNotesGenerator: ReleaseNotesGeneratorPlugin = require("@semantic-re export const PACKAGE_PATH_PREFIX = "packages/config/"; +/** + * Whether a write failed because the reader is gone. + * + * Node and Bun both tag this as `EPIPE` on the error object; the message text + * differs between them and is not matched. + */ +function isBrokenPipe(cause: unknown): boolean { + if (typeof cause !== "object" || cause === null || !("code" in cause)) { + return false; + } + return cause.code === "EPIPE"; +} + /** * Resolves which of `commits` touch a path under {@link PACKAGE_PATH_PREFIX}, * using ONE batched `git diff-tree --stdin -r --root --name-only -z` @@ -107,8 +120,21 @@ export async function filterCommitsToPackage( // is one runtime port away — don't rely on the buffering behavior. const stdoutText = new Response(proc.stdout).text(); const stderrText = new Response(proc.stderr).text(); - await proc.stdin.write(`${hashes.join("\n")}\n`); - await proc.stdin.end(); + // A `git` that rejects its arguments — a `cwd` outside any repository, say — + // exits before it reads a single hash, and writing to a process that has + // already gone raises EPIPE. Whether that happens is a race against process + // startup, so surfacing it would make the failure mode nondeterministic: + // sometimes `EPIPE: broken pipe, send`, sometimes the real diagnosis. The + // exit code and stderr below are the diagnosis, so a broken pipe here is + // dropped and the reporting left to them. + try { + await proc.stdin.write(`${hashes.join("\n")}\n`); + await proc.stdin.end(); + } catch (cause) { + if (!isBrokenPipe(cause)) { + throw cause; + } + } const [exitCode, stdout, stderr] = await Promise.all([proc.exited, stdoutText, stderrText]); if (exitCode !== 0) {