Compare commits

...

5 Commits

Author SHA1 Message Date
Claude Bot
11c30e1c16 test(terminal): use bunExe with Bun.sleepSync per review
Replace sleep command with bunExe + Bun.sleepSync(100000) to ensure
the same Bun build is used. sleepSync uses milliseconds, so 100000ms
= 100 seconds.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-06 15:39:51 +00:00
Claude Bot
b3a883e79a test(terminal): remove spawn-level timeout per review
Let test-runner handle timeouts instead of spawn-level timeout.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-06 02:43:20 +00:00
Claude Bot
8641bd393a test(terminal): add exitCode assertions for SIGINT tests
Address code review feedback by verifying that exitCode is null
when process is terminated by SIGINT signal.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-06 02:33:49 +00:00
Claude Bot
0e1816f53f test(terminal): increase sleep time for CI reliability
Increase sleep from 100ms to 300ms before sending Ctrl+C to ensure
the child process is fully started. This should fix flaky test on
darwin-13-aarch64.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-05 23:42:37 +00:00
Claude Bot
a750734fea fix(terminal): enable SIGINT/SIGTSTP signals for existing Terminal objects
When passing an existing `Bun.Terminal` object to `Bun.spawn()`, the child
process was not receiving signals from control characters (Ctrl+C, Ctrl+Z, etc.)
because `pty_slave_fd` was only passed for newly created terminals.

The fix ensures `pty_slave_fd` is passed for both existing and new terminals,
so the child process properly calls `setsid()` and `ioctl(TIOCSCTTY)` to become
the session leader with the PTY as its controlling terminal.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-05 23:09:13 +00:00
2 changed files with 61 additions and 2 deletions

View File

@@ -571,10 +571,12 @@ pub fn spawnMaybeSync(
.extra_fds = extra_fds.items,
.argv0 = argv0,
.can_block_entire_thread_to_reduce_cpu_usage_in_fast_path = can_block_entire_thread_to_reduce_cpu_usage_in_fast_path,
// Only pass pty_slave_fd for newly created terminals (for setsid+TIOCSCTTY setup).
// For existing terminals, the session is already set up - child just uses the fd as stdio.
// Pass pty_slave_fd for setsid+TIOCSCTTY setup so child becomes session leader
// with the PTY as its controlling terminal. This enables proper job control
// and signal handling (Ctrl+C -> SIGINT, Ctrl+Z -> SIGTSTP, etc.).
.pty_slave_fd = if (Environment.isPosix) blk: {
if (terminal_info) |ti| break :blk ti.terminal.getSlaveFd().native();
if (existing_terminal) |t| break :blk t.getSlaveFd().native();
break :blk -1;
} else {},

View File

@@ -1169,4 +1169,61 @@ describe.todoIf(isWindows)("Bun.spawn with terminal option", () => {
const output = Buffer.concat(dataChunks).toString();
expect(output).toContain("hello");
});
test("existing terminal sends SIGINT on Ctrl+C", async () => {
await using terminal = new Bun.Terminal({});
// Spawn a process that will run until interrupted (sleepSync uses milliseconds)
await using proc = Bun.spawn([bunExe(), "-e", "Bun.sleepSync(100000)"], { terminal, env: bunEnv });
// Wait for process to start - use longer delay for CI reliability
await Bun.sleep(300);
// Send Ctrl+C - should send SIGINT and interrupt sleep
terminal.write("\x03");
await proc.exited;
// SIGINT causes null exitCode with signalCode
expect(proc.exitCode).toBeNull();
expect(proc.signalCode).toBe("SIGINT");
});
test("existing terminal can be reused across multiple spawns", async () => {
await using terminal = new Bun.Terminal({});
// First spawn
await using proc1 = Bun.spawn(["echo", "first"], { terminal });
await proc1.exited;
expect(proc1.exitCode).toBe(0);
// Terminal should still be usable
expect(terminal.closed).toBe(false);
// Second spawn with same terminal
await using proc2 = Bun.spawn(["echo", "second"], { terminal });
await proc2.exited;
expect(proc2.exitCode).toBe(0);
// Both should have used the same terminal
expect(proc1.terminal).toBe(terminal);
expect(proc2.terminal).toBe(terminal);
});
test("existing terminal SIGINT works after reuse", async () => {
await using terminal = new Bun.Terminal({});
// First spawn - normal exit
await using proc1 = Bun.spawn(["echo", "first"], { terminal });
await proc1.exited;
expect(proc1.exitCode).toBe(0);
// Second spawn - interrupt with Ctrl+C (sleepSync uses milliseconds)
await using proc2 = Bun.spawn([bunExe(), "-e", "Bun.sleepSync(100000)"], { terminal, env: bunEnv });
await Bun.sleep(300);
terminal.write("\x03");
await proc2.exited;
expect(proc2.exitCode).toBeNull();
expect(proc2.signalCode).toBe("SIGINT");
});
});