fix(core): set windowsHide: true on all child process spawns#34455
fix(core): set windowsHide: true on all child process spawns#34455FrozenPandaz wants to merge 1 commit intomasterfrom
Conversation
❌ Deploy Preview for nx-docs failed. Why did it fail? →
|
✅ Deploy Preview for nx-dev ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
View your CI Pipeline Execution ↗ for commit 130efe4
☁️ Nx Cloud last updated this comment at |
| if (!optionsArg) { | ||
| // If there's a spread or variable reference we can't statically analyze, skip | ||
| const hasNonLiteralObject = node.arguments.some( | ||
| (arg) => | ||
| arg.type === 'Identifier' || | ||
| arg.type === 'SpreadElement' || | ||
| (arg.type === 'ObjectExpression' && | ||
| arg.properties.some((p) => p.type === 'SpreadElement')) | ||
| ); | ||
| // Only report if all args are literals and none is an options object | ||
| if (!hasNonLiteralObject) { | ||
| return; | ||
| } | ||
| return; | ||
| } |
There was a problem hiding this comment.
The logic for handling missing options objects is broken. When optionsArg is null/undefined, the code always returns without reporting an error, regardless of the hasNonLiteralObject check. This means the rule will never catch cases where spawn/exec/fork is called without any options object at all (e.g., spawn('echo', ['hello'])), which is exactly when windowsHide defaults to false and causes the flashing console windows on Windows.
// Current (broken):
if (!optionsArg) {
const hasNonLiteralObject = node.arguments.some(...);
if (!hasNonLiteralObject) {
return; // Returns here
}
return; // Or returns here - always returns!
}
// Fix:
if (!optionsArg) {
// Report error for missing options object
context.report({
messageId: 'missingWindowsHide',
node: node,
});
return;
}The rule should report an error when there's no options object, not silently skip validation.
| if (!optionsArg) { | |
| // If there's a spread or variable reference we can't statically analyze, skip | |
| const hasNonLiteralObject = node.arguments.some( | |
| (arg) => | |
| arg.type === 'Identifier' || | |
| arg.type === 'SpreadElement' || | |
| (arg.type === 'ObjectExpression' && | |
| arg.properties.some((p) => p.type === 'SpreadElement')) | |
| ); | |
| // Only report if all args are literals and none is an options object | |
| if (!hasNonLiteralObject) { | |
| return; | |
| } | |
| return; | |
| } | |
| if (!optionsArg) { | |
| // Report error for missing options object | |
| context.report({ | |
| messageId: 'missingWindowsHide', | |
| node: node, | |
| }); | |
| return; | |
| } |
Spotted by Graphite Agent
Is this helpful? React 👍 or 👎 to let us know.
There was a problem hiding this comment.
Nx Cloud is proposing a fix for your failed CI:
These changes fix the TypeScript compilation errors caused by the windowsHide property not being recognized in ForkOptions. We extended the ForkOptions type to include windowsHide and applied type assertions to all fork calls, allowing the PR's intent (hiding console windows on Windows) to work while satisfying TypeScript's type checker.
Tip
✅ We verified this fix by re-running angular-rspack:typecheck, nx:build-base, dotnet:test and 15 more.
Suggested Fix changes
diff --git a/packages/nx/src/tasks-runner/fork.ts b/packages/nx/src/tasks-runner/fork.ts
index 9ecac3e555..e348291524 100644
--- a/packages/nx/src/tasks-runner/fork.ts
+++ b/packages/nx/src/tasks-runner/fork.ts
@@ -1,8 +1,11 @@
-import { fork, Serializable } from 'child_process';
+import { fork, ForkOptions, Serializable } from 'child_process';
import { join } from 'path';
import { PseudoIPCClient } from './pseudo-ipc';
import { signalToCode } from '../utils/exit-codes';
+// Extend ForkOptions to include windowsHide which exists at runtime but is missing from @types/node ForkOptions
+type ForkOptionsWithWindowsHide = ForkOptions & { windowsHide?: boolean };
+
const pseudoIPCPath = process.argv[2];
const forkId = process.argv[3];
@@ -19,7 +22,7 @@ const childProcess = fork(script, {
env: process.env,
execArgv,
windowsHide: true,
-});
+} as ForkOptionsWithWindowsHide);
const pseudoIPC = new PseudoIPCClient(pseudoIPCPath);
diff --git a/packages/nx/src/tasks-runner/forked-process-task-runner.ts b/packages/nx/src/tasks-runner/forked-process-task-runner.ts
index 626d2df867..386c94692e 100644
--- a/packages/nx/src/tasks-runner/forked-process-task-runner.ts
+++ b/packages/nx/src/tasks-runner/forked-process-task-runner.ts
@@ -1,4 +1,4 @@
-import { fork, Serializable } from 'child_process';
+import { fork, ForkOptions, Serializable } from 'child_process';
import { writeFileSync } from 'fs';
import { join } from 'path';
import { ProjectGraph } from '../config/project-graph';
@@ -25,6 +25,9 @@ import { registerTaskProcessStart } from './task-io-service';
import { Batch } from './tasks-schedule';
import { getCliPath, getPrintableCommandArgsForTask } from './utils';
+// Extend ForkOptions to include windowsHide which exists at runtime but is missing from @types/node ForkOptions
+type ForkOptionsWithWindowsHide = ForkOptions & { windowsHide?: boolean };
+
const forkScript = join(__dirname, './fork.js');
const workerPath = join(__dirname, './batch/run-batch.js');
@@ -74,7 +77,7 @@ export class ForkedProcessTaskRunner {
NX_FORKED_TASK_EXECUTOR: 'true',
},
windowsHide: true,
- });
+ } as ForkOptionsWithWindowsHide);
// Register batch worker process with all tasks
if (p.pid) {
@@ -297,7 +300,7 @@ export class ForkedProcessTaskRunner {
NX_FORKED_TASK_EXECUTOR: 'true',
},
windowsHide: true,
- });
+ } as ForkOptionsWithWindowsHide);
// Register forked process for metrics collection
if (p.pid) {
@@ -364,7 +367,7 @@ export class ForkedProcessTaskRunner {
NX_FORKED_TASK_EXECUTOR: 'true',
},
windowsHide: true,
- });
+ } as ForkOptionsWithWindowsHide);
// Register forked process for metrics collection
if (p.pid) {
Or Apply changes locally with:
npx nx-cloud apply-locally NnND-idhs
Apply fix locally with your editor ↗ View interactive diff ↗
🎓 Learn more about Self-Healing CI on nx.dev
On Windows, child_process.spawn/fork/exec default to windowsHide: false, which creates a visible console window for each subprocess. This causes command prompt windows to flash during project graph creation and task execution. This change sets windowsHide: true across all spawn/fork/exec calls in the codebase and adds a custom ESLint rule (@nx/workspace/require-windows-hide) to enforce this going forward. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Current Behavior
On Windows,
child_process.spawn/fork/execcalls default towindowsHide: false, which creates a visible console window for each subprocess. This causes command prompt windows to flash on screen during project graph creation (daemon spawn), task execution, cache cleanup, and many other operations.Expected Behavior
No console windows should flash on Windows. All child process spawns use
windowsHide: trueto suppress the console window, and a custom ESLint rule enforces this going forward.Changes
windowsHide: trueon allspawn/fork/exec/execSync/spawnSynccalls across the entire codebase (~123 files)@nx/workspace/require-windows-hidethat:spawn/fork/execcall has an options object missingwindowsHide: truewindowsHideis explicitly set tofalserun-commands.impl.spec.tsto matchRelated Issue(s)
🤖 Generated with Claude Code