Compare commits

...

1 Commits

Author SHA1 Message Date
Dylan Conway
61c1ea8969 fix(shell): handle unrecognized errno in cd instead of hanging
When `cd` encounters an unrecognized errno (e.g. ENAMETOOLONG for path
components > 255 bytes), handleChangeCwdErr returned `.failed` which
means "JS error was thrown" but no error was actually thrown. This
caused the shell trampoline to terminate without completing the command,
hanging the shell indefinitely.

Now the else branch looks up a human-readable error message via
coreutils_error_map and writes it to stderr, matching the behavior
of the NOTDIR/NOENT cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 02:47:11 +00:00
2 changed files with 45 additions and 1 deletions

View File

@@ -87,7 +87,16 @@ fn handleChangeCwdErr(this: *Cd, err: Syscall.Error, new_cwd_: []const u8) Yield
return this.writeStderrNonBlocking("not a directory: {s}\n", .{new_cwd_});
},
else => return .failed,
else => {
const message: []const u8 = brk: {
if (err.getErrorCodeTagName()) |entry| {
_, const sys_errno = entry;
if (Syscall.coreutils_error_map.get(sys_errno)) |msg| break :brk msg;
}
break :brk "unknown error";
};
return this.writeStderrNonBlocking("{s}: {s}\n", .{ new_cwd_, message });
},
}
}

View File

@@ -0,0 +1,35 @@
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, isPosix } from "harness";
// Regression test: cd with a path component exceeding NAME_MAX (255 bytes)
// triggers ENAMETOOLONG from openat(). The handleChangeCwdErr function
// had `else => return .failed` which means "JS error was thrown" but
// no error was actually thrown, causing the shell to hang indefinitely.
describe.if(isPosix)("cd with path exceeding NAME_MAX should not hang", () => {
test("cd returns error for path component > 255 chars", async () => {
const longComponent = Buffer.alloc(256, "A").toString();
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`
import { $ } from "bun";
$.throws(false);
const r = await $\`cd ${longComponent}\`;
console.log("exitCode:" + r.exitCode);
`,
],
env: { ...bunEnv, longComponent },
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toContain("exitCode:1");
expect(stderr).toContain("File name too long");
expect(exitCode).toBe(0);
});
});