Skip to content

fix(core): set windowsHide: true on all child process spawns#34455

Draft
FrozenPandaz wants to merge 1 commit intomasterfrom
fix/windows-hide-true
Draft

fix(core): set windowsHide: true on all child process spawns#34455
FrozenPandaz wants to merge 1 commit intomasterfrom
fix/windows-hide-true

Conversation

@FrozenPandaz
Copy link
Collaborator

Current Behavior

On Windows, child_process.spawn/fork/exec calls default to windowsHide: 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: true to suppress the console window, and a custom ESLint rule enforces this going forward.

Changes

  • Set windowsHide: true on all spawn/fork/exec/execSync/spawnSync calls across the entire codebase (~123 files)
  • Added custom ESLint rule @nx/workspace/require-windows-hide that:
    • Errors when any spawn/fork/exec call has an options object missing windowsHide: true
    • Errors when windowsHide is explicitly set to false
    • Skips test/spec files
  • Updated test expectations in run-commands.impl.spec.ts to match

Related Issue(s)

🤖 Generated with Claude Code

@netlify
Copy link

netlify bot commented Feb 14, 2026

Deploy Preview for nx-docs failed. Why did it fail? →

Name Link
🔨 Latest commit 130efe4
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/698ffe3cf0dddf0008d19fcb

@netlify
Copy link

netlify bot commented Feb 14, 2026

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit 130efe4
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/698ffe3cd8fcb4000810ccb4
😎 Deploy Preview https://deploy-preview-34455--nx-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@nx-cloud
Copy link
Contributor

nx-cloud bot commented Feb 14, 2026

View your CI Pipeline Execution ↗ for commit 130efe4

Command Status Duration Result
nx affected --targets=lint,test,test-kt,build,e... ❌ Failed 8m 14s View ↗
nx affected -t e2e-macos-local --parallel=1 --b... ❌ Failed 7m 6s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 2m 48s View ↗
nx-cloud record -- nx-cloud conformance:check ✅ Succeeded 8s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 5s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2026-02-14 05:13:33 UTC

Comment on lines +55 to +69
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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Copy link
Contributor

@nx-cloud nx-cloud bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {

Apply fix via Nx Cloud  Reject fix via Nx Cloud


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

@FrozenPandaz FrozenPandaz marked this pull request as draft February 14, 2026 06:28
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant