Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
623234fa6a fix(shell): builtin rm returns wrong exit code in quiet mode
The `rm` builtin's `next()` function had a hardcoded `return this.bltn().done(0)`
for the `.done` state, ignoring the exit code stored in `this.state.done.exit_code`.
This caused `.quiet()` and `.text()` to always report exit code 0 for `rm` failures,
since the quiet code path goes through `next()` rather than `onIOWriterChunk()`.

Also fixed `onShellRmTaskDone` to use exit code 1 (matching POSIX convention) instead
of the raw errno value.

Closes #18161

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-20 10:31:11 +00:00
2 changed files with 48 additions and 2 deletions

View File

@@ -289,7 +289,7 @@ pub noinline fn next(this: *Rm) Yield {
}
switch (this.state) {
.done => return this.bltn().done(0),
.done => return this.bltn().done(this.state.done.exit_code),
.err => return this.bltn().done(this.state.err),
else => unreachable,
}
@@ -430,7 +430,7 @@ pub fn onShellRmTaskDone(this: *Rm, task: *ShellRmTask) void {
if (tasks_done >= this.state.exec.total_tasks and
exec.getOutputCount(.output_done) >= exec.getOutputCount(.output_count))
{
this.state = .{ .done = .{ .exit_code = if (exec.err) |theerr| theerr.errno else 0 } };
this.state = .{ .done = .{ .exit_code = if (exec.err != null) 1 else 0 } };
this.next().run();
}
}

View File

@@ -0,0 +1,46 @@
import { $ } from "bun";
import { describe, expect, test } from "bun:test";
import { tempDir } from "harness";
describe("shell .quiet() should preserve exit codes", () => {
test("builtin rm with .quiet() throws on failure", async () => {
using dir = tempDir("issue-18161", {});
try {
await $`rm ${dir}/nonexistent-file.txt`.quiet();
expect.unreachable();
} catch (e: any) {
expect(e.exitCode).not.toBe(0);
}
});
test("builtin rm with .nothrow().quiet() returns non-zero exit code", async () => {
using dir = tempDir("issue-18161", {});
const result = await $`rm ${dir}/nonexistent-file.txt`.nothrow().quiet();
expect(result.exitCode).not.toBe(0);
});
test("builtin rm with .text() throws on failure", async () => {
using dir = tempDir("issue-18161", {});
try {
await $`rm ${dir}/nonexistent-file.txt`.text();
expect.unreachable();
} catch (e: any) {
expect(e.exitCode).not.toBe(0);
}
});
test("builtin rm with .quiet() returns 0 on success", async () => {
using dir = tempDir("issue-18161", {
"existing-file.txt": "hello",
});
const result = await $`rm ${dir}/existing-file.txt`.nothrow().quiet();
expect(result.exitCode).toBe(0);
});
test("builtin rm exit code matches between quiet and non-quiet", async () => {
using dir = tempDir("issue-18161", {});
const nonQuiet = await $`rm ${dir}/nonexistent-file.txt`.nothrow();
const quiet = await $`rm ${dir}/nonexistent-file.txt`.nothrow().quiet();
expect(quiet.exitCode).toBe(nonQuiet.exitCode);
});
});