From e9e40d5f00fae8305610208d76ec32ff22b55237 Mon Sep 17 00:00:00 2001 From: Victor Cardoso Date: Sat, 14 Feb 2026 18:55:48 -0300 Subject: [PATCH 1/2] watch: remove NODE_OPTIONS from spawn call to prevent infinite loop --- lib/internal/main/watch_mode.js | 11 ++- .../test-watch-mode-watch-flags.mjs | 74 ++++++++++++++++++- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/lib/internal/main/watch_mode.js b/lib/internal/main/watch_mode.js index 225436661f5e56..02bf2fb19b40db 100644 --- a/lib/internal/main/watch_mode.js +++ b/lib/internal/main/watch_mode.js @@ -95,12 +95,15 @@ let exited; function start() { exited = false; const stdio = kShouldFilterModules ? ['inherit', 'inherit', 'inherit', 'ipc'] : 'inherit'; + const env = { + ...process.env, + WATCH_REPORT_DEPENDENCIES: '1', + }; + + delete env.NODE_OPTIONS; child = spawn(process.execPath, argsWithoutWatchOptions, { stdio, - env: { - ...process.env, - WATCH_REPORT_DEPENDENCIES: '1', - }, + env, }); watcher.watchChildProcessModules(child); if (kEnvFiles.length > 0) { diff --git a/test/sequential/test-watch-mode-watch-flags.mjs b/test/sequential/test-watch-mode-watch-flags.mjs index 385f381ad6a0ed..dd9524c2109b5b 100644 --- a/test/sequential/test-watch-mode-watch-flags.mjs +++ b/test/sequential/test-watch-mode-watch-flags.mjs @@ -5,7 +5,7 @@ import path from 'node:path'; import { execPath } from 'node:process'; import { describe, it } from 'node:test'; import { spawn } from 'node:child_process'; -import { writeFileSync, mkdirSync } from 'node:fs'; +import { writeFileSync, mkdirSync, chmodSync } from 'node:fs'; import { inspect } from 'node:util'; import { createInterface } from 'node:readline'; @@ -48,6 +48,44 @@ async function runNode({ return { stdout, stderr, pid: child.pid }; } +async function runExecutable({ + file, + expectedCompletionLog = 'Completed running', + options = {}, + timeout = common.platformTimeout(10_000), +}) { + const child = spawn(file, [], { encoding: 'utf8', stdio: 'pipe', ...options }); + let stderr = ''; + const stdout = []; + let timedOut = true; + + child.stderr.on('data', (data) => { + stderr += data; + }); + + const timer = setTimeout(() => { + child.kill(); + }, timeout); + + try { + for await (const data of createInterface({ input: child.stdout })) { + if (!data.startsWith('Waiting for graceful termination') && + !data.startsWith('Gracefully restarted')) { + stdout.push(data); + } + if (data.startsWith(expectedCompletionLog)) { + timedOut = false; + break; + } + } + } finally { + clearTimeout(timer); + child.kill(); + } + + return { stdout, stderr, timedOut }; +} + tmpdir.refresh(); describe('watch mode - watch flags', { concurrency: !process.env.TEST_PARALLEL, timeout: 60_000 }, () => { @@ -94,4 +132,38 @@ describe('watch mode - watch flags', { concurrency: !process.env.TEST_PARALLEL, `Completed running ${inspect(file)}. Waiting for file changes before restarting...`, ]); }); + + it('should not recursively re-enter watch mode for shebang scripts when NODE_OPTIONS=--watch', + { skip: common.isWindows || process.config.variables.node_without_node_options }, + async () => { + const projectDir = tmpdir.resolve('project-watch-node-options-shebang'); + mkdirSync(projectDir); + + const file = createTmpFile( + '#!/usr/bin/env node\nconsole.log("shebang run");\n', + '.js', + projectDir, + ); + chmodSync(file, 0o755); + + const { stdout, stderr, timedOut } = await runExecutable({ + file, + options: { + cwd: projectDir, + env: { + ...process.env, + NODE_OPTIONS: '--watch', + // Ensure shebang resolves this test binary, not system node. + PATH: `${path.dirname(execPath)}${path.delimiter}${process.env.PATH ?? ''}`, + }, + }, + }); + + assert.strictEqual(timedOut, false); + assert.strictEqual(stderr, ''); + assert.deepStrictEqual(stdout, [ + 'shebang run', + `Completed running ${inspect(file)}. Waiting for file changes before restarting...`, + ]); + }); }); From 8ed076a86b11ae406ff311972a338e6280324741 Mon Sep 17 00:00:00 2001 From: Victor Cardoso Date: Sun, 15 Feb 2026 18:30:04 -0300 Subject: [PATCH 2/2] watch: fix lint --- .../test-watch-mode-watch-flags.mjs | 63 +++++++++---------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/test/sequential/test-watch-mode-watch-flags.mjs b/test/sequential/test-watch-mode-watch-flags.mjs index dd9524c2109b5b..1d18d6297f7011 100644 --- a/test/sequential/test-watch-mode-watch-flags.mjs +++ b/test/sequential/test-watch-mode-watch-flags.mjs @@ -134,36 +134,35 @@ describe('watch mode - watch flags', { concurrency: !process.env.TEST_PARALLEL, }); it('should not recursively re-enter watch mode for shebang scripts when NODE_OPTIONS=--watch', - { skip: common.isWindows || process.config.variables.node_without_node_options }, - async () => { - const projectDir = tmpdir.resolve('project-watch-node-options-shebang'); - mkdirSync(projectDir); - - const file = createTmpFile( - '#!/usr/bin/env node\nconsole.log("shebang run");\n', - '.js', - projectDir, - ); - chmodSync(file, 0o755); - - const { stdout, stderr, timedOut } = await runExecutable({ - file, - options: { - cwd: projectDir, - env: { - ...process.env, - NODE_OPTIONS: '--watch', - // Ensure shebang resolves this test binary, not system node. - PATH: `${path.dirname(execPath)}${path.delimiter}${process.env.PATH ?? ''}`, - }, - }, - }); - - assert.strictEqual(timedOut, false); - assert.strictEqual(stderr, ''); - assert.deepStrictEqual(stdout, [ - 'shebang run', - `Completed running ${inspect(file)}. Waiting for file changes before restarting...`, - ]); - }); + { skip: common.isWindows || process.config.variables.node_without_node_options }, + async () => { + const projectDir = tmpdir.resolve('project-watch-node-options-shebang'); + mkdirSync(projectDir); + + const file = createTmpFile( + '#!/usr/bin/env node\nconsole.log("shebang run");\n', + '.js', + projectDir, + ); + chmodSync(file, 0o755); + + const { stdout, stderr, timedOut } = await runExecutable({ + file, + options: { + cwd: projectDir, + env: { + ...process.env, + NODE_OPTIONS: '--watch', + PATH: `${path.dirname(execPath)}${path.delimiter}${process.env.PATH ?? ''}`, + }, + }, + }); + + assert.strictEqual(timedOut, false); + assert.strictEqual(stderr, ''); + assert.deepStrictEqual(stdout, [ + 'shebang run', + `Completed running ${inspect(file)}. Waiting for file changes before restarting...`, + ]); + }); });