Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions packages/config/scripts/semantic-release-path-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -107,8 +120,21 @@ export async function filterCommitsToPackage<T extends { hash: string }>(
// 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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MINOR · error-handling · source: claude

A caught EPIPE followed by a zero git exit code could allow parsing incomplete stdout without detecting truncated input.

Evidence: packages/config/scripts/semantic-release-path-filter.ts:133-137 suppresses EPIPE, while lines 139-159 reject only nonzero exits and otherwise parse and return the observed hashes.

Suggested fix: Track whether the write failed and reject a zero-exit result unless completeness can otherwise be established.

Adjudication (uncertain): The control flow permits the claimed combination, but the harmful outcome requires git diff-tree to close stdin and exit successfully before consuming all hashes. Neither the implementation nor tests establish that this can occur, so silent truncation could not be verified.

if (!isBrokenPipe(cause)) {
throw cause;
}
}

const [exitCode, stdout, stderr] = await Promise.all([proc.exited, stdoutText, stderrText]);
if (exitCode !== 0) {
Expand Down
Loading