Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
840beed3f1 Fix shell cat panic on Windows when reader chunks arrive late
Fixes a race condition where IO reader chunks could arrive after the cat
builtin transitioned to terminal states (.done, .waiting_write_err, .idle),
causing a panic with "Invalid state". This was particularly reproducible on
Windows in package scripts.

The fix handles these terminal states gracefully in onIOReaderChunk() by
ignoring late-arriving chunks since the reader is already being cleaned up.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-09 07:44:09 +00:00
2 changed files with 57 additions and 2 deletions

View File

@@ -196,9 +196,10 @@ pub fn onIOReaderChunk(this: *Cat, chunk: []const u8, remove: *bool) Yield {
_ = this.bltn().writeNoIO(.stdout, chunk);
return .done;
},
else => @panic("Invalid state"),
// Race condition: chunks can arrive after state transitions (especially on Windows)
// Just ignore them - the reader is being cleaned up
.done, .waiting_write_err, .idle => return .done,
}
return .done;
}
pub fn onIOReaderDone(this: *Cat, err: ?jsc.SystemError) Yield {

View File

@@ -0,0 +1,54 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
test("cat should not panic on delayed reader chunks (issue #23370)", async () => {
const dir = tempDirWithFiles("cat-race", {
"file1.txt": "a".repeat(1024 * 1024), // 1MB
"file2.txt": "b".repeat(1024 * 1024), // 1MB
"file3.txt": "c".repeat(1024 * 1024), // 1MB
"test.js": `
const { $ } = require("bun");
await $\`cat file1.txt file2.txt file3.txt\`;
`,
});
const proc = Bun.spawn({
cmd: [bunExe(), "test.js"],
cwd: dir,
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [exitCode, stderr] = await Promise.all([proc.exited, proc.stderr.text()]);
expect(stderr).not.toContain("panic");
expect(stderr).not.toContain("Invalid state");
expect(exitCode).toBe(0);
}, 10000);
test("cat in package script should not panic", async () => {
const dir = tempDirWithFiles("cat-pkgscript", {
"file.txt": "x".repeat(1024 * 1024), // 1MB
"package.json": JSON.stringify({
name: "test",
scripts: {
test: "cat file.txt",
},
}),
});
const proc = Bun.spawn({
cmd: [bunExe(), "run", "test"],
cwd: dir,
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [exitCode, stderr] = await Promise.all([proc.exited, proc.stderr.text()]);
expect(stderr).not.toContain("panic");
expect(stderr).not.toContain("Invalid state");
expect(exitCode).toBe(0);
}, 10000);