Skip to content

Commit 0b00bc8

Browse files
authored
fix(watch): exit with 128 + signal number when interrupted (#820)
When a signal is received after the watched script has already exited, watch mode exited with the raw signal number (2 for SIGINT), which PowerShell & VS Code on Windows surface as an error. Use the standard 128 + signal convention, consistent with the non-watch CLI.
1 parent 3216332 commit 0b00bc8

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

src/watch/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,10 @@ export const watchCommand = command({
270270
() => {},
271271
);
272272
} else {
273+
// Exit with 128 + signal number, as done by Node.js & POSIX shells
274+
// https://nodejs.org/api/process.html#exit-codes
273275
// eslint-disable-next-line n/no-process-exit
274-
process.exit(osConstants.signals[signal]);
276+
process.exit(128 + osConstants.signals[signal]);
275277
}
276278
};
277279

tests/specs/watch.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,42 @@ export const watch = ({ tsx, path: nodePath }: NodeApis) => describe('watch', as
401401
expect(all).toMatch(/start[\s\S]+end/);
402402
}, 10_000);
403403

404+
await describe('Ctrl + C', async () => {
405+
const CtrlC = '\u0003';
406+
407+
await test('exits with 130 when script already exited (issue #734)', async () => {
408+
await using fixtureExited = await createFixture({
409+
'index.js': 'console.log("READY")',
410+
});
411+
412+
await using shell = ptyShell();
413+
414+
onTestFail(() => {
415+
console.log({ stdout: shell.getOutput() });
416+
});
417+
418+
await shell.waitForPrompt();
419+
// PowerShell needs the call operator to run a quoted command
420+
shell.type(`${isWindows ? '& ' : ''}"${nodePath}" "${tsxPath}" watch "${fixtureExited.getPath('index.js')}"`);
421+
422+
await shell.waitForLine(/READY/);
423+
424+
// Wait for the child process to exit so the watcher is idle
425+
await setTimeout(1000);
426+
shell.press(CtrlC);
427+
428+
await shell.waitForPrompt();
429+
shell.type(`echo EXIT_CODE: ${isWindows ? '$LastExitCode' : '$?'}`);
430+
431+
await shell.waitForPrompt();
432+
433+
expect(await shell.close()).toMatch(/EXIT_CODE:\s+130/);
434+
}, {
435+
timeout: 15_000,
436+
retry: 3,
437+
});
438+
});
439+
404440
await describe('help', () => {
405441
test('shows help', async () => {
406442
const tsxProcess = await tsx(['watch', '--help']);

0 commit comments

Comments
 (0)