diff --git a/src/cli/install_completions_command.zig b/src/cli/install_completions_command.zig index dde44ec360..c9f83b0cf8 100644 --- a/src/cli/install_completions_command.zig +++ b/src/cli/install_completions_command.zig @@ -138,7 +138,10 @@ pub const InstallCompletionsCommand = struct { // don't fail on this if we don't actually need to if (fail_exit_code == 1) { if (!stdout.isTty()) { - try stdout.writeAll(shell.completions()); + stdout.writeAll(shell.completions()) catch |err| switch (err) { + error.BrokenPipe => Global.exit(0), + else => return err, + }; Global.exit(0); } } @@ -171,7 +174,10 @@ pub const InstallCompletionsCommand = struct { if (!bun.env_var.IS_BUN_AUTO_UPDATE.get()) { if (!stdout.isTty()) { - try stdout.writeAll(shell.completions()); + stdout.writeAll(shell.completions()) catch |err| switch (err) { + error.BrokenPipe => Global.exit(0), + else => return err, + }; Global.exit(0); } } diff --git a/test/regression/issue/02977.test.ts b/test/regression/issue/02977.test.ts new file mode 100644 index 0000000000..176181c825 --- /dev/null +++ b/test/regression/issue/02977.test.ts @@ -0,0 +1,22 @@ +import { expect, test } from "bun:test"; +import { bunEnv, bunExe, isPosix } from "harness"; + +// https://github.com/oven-sh/bun/issues/2977 +test.if(isPosix)("bun completions handles BrokenPipe gracefully", async () => { + // Simulate piping to a command that closes stdin immediately (like `true`) + // This tests that bun completions doesn't crash with BrokenPipe error + await using proc = Bun.spawn({ + cmd: ["sh", "-c", `SHELL=/bin/bash ${bunExe()} completions | true`], + env: bunEnv, + stderr: "pipe", + stdout: "pipe", + }); + + const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + // Should exit cleanly (0) instead of crashing with BrokenPipe error + // The stderr should NOT contain "BrokenPipe" error + expect(stderr).not.toContain("BrokenPipe"); + expect(stderr).not.toContain("error"); + expect(exitCode).toBe(0); +});