Skip to content

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

Merged
FrozenPandaz merged 9 commits intomasterfrom
fix/windows-hide-true
Mar 20, 2026
Merged

fix(core): set windowsHide: true on all child process spawns#34894
FrozenPandaz merged 9 commits intomasterfrom
fix/windows-hide-true

Conversation

@FrozenPandaz
Copy link
Collaborator

@FrozenPandaz FrozenPandaz commented Mar 18, 2026

Current Behavior

On Windows, the Nx daemon runs as a detached background process with no console. When child processes are spawned without windowsHide: true (Node.js) or CREATE_NO_WINDOW (Rust/Win32), Windows allocates a new visible console window for each subprocess. This causes command prompt windows to flash on screen during:

  • Project graph creation (daemon spawn, plugin workers)
  • Task hashing (runtime hashers)
  • NX Console extension detection (code.cmd --list-extensions, etc.)
  • AI agent configuration checks (git ls-remote, npm install)
  • Machine ID retrieval, package manager version detection, git operations, and more

The issue is especially noticeable "after a little bit" following daemon startup, because background operations like the NX Console status check and AI agents configuration check kick off after the initial project graph is computed.

Expected Behavior

No console windows should flash on Windows. All child process spawns use windowsHide: true (Node.js) or CREATE_NO_WINDOW (Rust) to suppress console windows.

Root Causes Found

Investigation using a child_process interceptor in the daemon revealed multiple sources:

  1. Rust native ide/install.rsCommand::new("code.cmd") calls for NX Console extension detection/installation were missing CREATE_NO_WINDOW
  2. Rust native hash_runtime.rs — Had its own CREATE_NO_WINDOW handling but was duplicated
  3. nx@latest temp install — The daemon downloads nx@latest to a temp directory for NX Console and AI agent checks. The install process (pnpm add -D nx@latest) and the downloaded code's git ls-remote calls run without windowsHide: true
  4. ~120 Node.js child_process calls — Various spawn/exec/execSync calls across the codebase were missing windowsHide: true

Changes

Node.js child_process fixes

  • Set windowsHide: true on all spawn/exec/execSync/spawnSync calls across the codebase (~120 files)
  • Added custom ESLint rule @nx/workspace/require-windows-hide that errors when any spawn/exec call is missing windowsHide: true

Rust native fixes

  • New shared util native/utils/command.rs with create_command() and create_shell_command() that set CREATE_NO_WINDOW on Windows — centralizes the pattern so future Rust code gets it right by default
  • ide/install.rs — Use create_command() for code.cmd calls (list-extensions, install-extension, version check)
  • hash_runtime.rs — Replaced local create_command_builder() with shared create_shell_command()
  • machine_id/mod.rs — Updated to use shared create_shell_command()

Daemon background operation fixes

  • handle-configure-ai-agents.ts — Now respects NX_USE_LOCAL env var to skip downloading nx@latest, avoiding the pnpm install that opens windows. Once these fixes ship in a release, the downloaded nx@latest will also have the fixes.

Inlined node-machine-id

  • Replaced the node-machine-id npm package with an inlined implementation in machine-id-cache.ts that uses windowsHide: true
  • The original package used exec/execSync without windowsHide

Related Issue(s)

Supersedes #34455

@FrozenPandaz FrozenPandaz requested a review from a team as a code owner March 18, 2026 01:25
@FrozenPandaz FrozenPandaz requested a review from MaxKless March 18, 2026 01:25
@netlify
Copy link

netlify bot commented Mar 18, 2026

Deploy Preview for nx-dev ready!

Name Link
🔨 Latest commit 827890d
🔍 Latest deploy log https://app.netlify.com/projects/nx-dev/deploys/69bccf52c9e0cd000872e201
😎 Deploy Preview https://deploy-preview-34894--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.

@netlify
Copy link

netlify bot commented Mar 18, 2026

Deploy Preview for nx-docs ready!

Name Link
🔨 Latest commit 827890d
🔍 Latest deploy log https://app.netlify.com/projects/nx-docs/deploys/69bccf52cb242200080fae50
😎 Deploy Preview https://deploy-preview-34894--nx-docs.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 Mar 18, 2026

View your CI Pipeline Execution ↗ for commit 827890d

Command Status Duration Result
nx affected --targets=lint,test,build,e2e,e2e-c... ✅ Succeeded 12m 32s View ↗
nx run-many -t check-imports check-lock-files c... ✅ Succeeded 10s View ↗
nx-cloud record -- nx-cloud conformance:check ✅ Succeeded 8s View ↗
nx-cloud record -- nx format:check ✅ Succeeded 4s View ↗
nx-cloud record -- nx sync:check ✅ Succeeded <1s View ↗
nx affected -t e2e-macos-local --parallel=1 --b... ✅ Succeeded 2m 52s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-20 04:55:24 UTC

Comment on lines +54 to +68
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 argument is broken. When optionsArg is not found, the code checks for non-literal objects but then returns in both branches, meaning no error is ever reported when the options object is completely missing.

For example, spawn('echo', ['hello']) would pass this rule even though it's missing windowsHide: true.

Fix:

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'))
  );
  if (hasNonLiteralObject) {
    // Can't analyze statically, skip
    return;
  }
  // No options object provided at all - report error
  context.report({
    messageId: 'missingWindowsHide',
    node: node,
  });
  return;
}

This bug undermines the entire purpose of the ESLint rule since it won't catch the most common case of missing windowsHide.

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) {
// 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'))
);
if (hasNonLiteralObject) {
// Can't analyze statically, skip
return;
}
// No options object provided at all - report error
context.report({
messageId: 'missingWindowsHide',
node: node,
});
return;
}

Spotted by Graphite

Fix in Graphite


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

nx-cloud[bot]

This comment was marked as outdated.

@FrozenPandaz FrozenPandaz force-pushed the fix/windows-hide-true branch from 3b101a0 to 020ab80 Compare March 18, 2026 01:48
Comment on lines +56 to +62
const hasNonLiteralObject = node.arguments.some(
(arg) =>
arg.type === 'Identifier' ||
arg.type === 'SpreadElement' ||
(arg.type === 'ObjectExpression' &&
arg.properties.some((p) => p.type === 'SpreadElement'))
);
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 incorrectly skips validation when ANY argument is an Identifier, not just the options. This means common patterns like spawn(command, args) where command is a variable would be skipped even though options are completely missing.

// Current behavior - incorrectly skips this:
const cmd = 'echo';
spawn(cmd, ['hello']); // Should error but won't because cmd is an Identifier

The check should only consider whether the options argument itself (typically the last argument) could be a variable, not all arguments. Fix:

// Check if the last argument could be options passed as variable
const lastArg = node.arguments[node.arguments.length - 1];
const hasVariableOptions = lastArg && (
  lastArg.type === 'Identifier' ||
  lastArg.type === 'SpreadElement'
);
if (hasVariableOptions) {
  return;
}
Suggested change
const hasNonLiteralObject = node.arguments.some(
(arg) =>
arg.type === 'Identifier' ||
arg.type === 'SpreadElement' ||
(arg.type === 'ObjectExpression' &&
arg.properties.some((p) => p.type === 'SpreadElement'))
);
const lastArg = node.arguments[node.arguments.length - 1];
const hasVariableOptions = lastArg && (
lastArg.type === 'Identifier' ||
lastArg.type === 'SpreadElement'
);
if (hasVariableOptions) {
return;
}

Spotted by Graphite

Fix in Graphite


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

@FrozenPandaz FrozenPandaz force-pushed the fix/windows-hide-true branch from 020ab80 to 55db83d Compare March 18, 2026 02:02
nx-cloud[bot]

This comment was marked as outdated.

@github-actions
Copy link
Contributor

Failed to publish a PR release of this pull request, triggered by @FrozenPandaz.
See the failed workflow run at: https://github.com/nrwl/nx/actions/runs/23225734000

@FrozenPandaz FrozenPandaz force-pushed the fix/windows-hide-true branch 2 times, most recently from 690997e to dac84ab Compare March 18, 2026 15:05
@github-actions
Copy link
Contributor

Failed to publish a PR release of this pull request, triggered by @FrozenPandaz.
See the failed workflow run at: https://github.com/nrwl/nx/actions/runs/23225734000

@FrozenPandaz FrozenPandaz force-pushed the fix/windows-hide-true branch 3 times, most recently from 4924d24 to b6f7688 Compare March 18, 2026 15:32
nx-cloud[bot]

This comment was marked as outdated.

@FrozenPandaz FrozenPandaz force-pushed the fix/windows-hide-true branch 3 times, most recently from dee4348 to f872491 Compare March 19, 2026 03:21
@github-actions
Copy link
Contributor

🐳 We have a release for that!

This PR has a release associated with it. You can try it out using this command:

npx create-nx-workspace@22.6.0-pr.34894.f872491 my-workspace

Or just copy this version and use it in your own command:

22.6.0-pr.34894.f872491
Release details 📑
Published version 22.6.0-pr.34894.f872491
Triggered by @FrozenPandaz
Branch fix/windows-hide-true
Commit f872491
Workflow run 23278289990

To request a new release for this pull request, mention someone from the Nx team or the @nrwl/nx-pipelines-reviewers.

nx-cloud[bot]

This comment was marked as outdated.

nx-cloud[bot]

This comment was marked as outdated.

FrozenPandaz and others added 5 commits March 19, 2026 17:15
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>
ForkOptions in @types/node does not include windowsHide, causing
typecheck failures. Since fork() spawns Node.js processes (not
shell commands), there is no console window to hide on Windows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
node-machine-id uses exec/execSync without windowsHide, causing
console windows to appear on Windows when called from detached
processes like the daemon or plugin workers.
FrozenPandaz and others added 2 commits March 19, 2026 17:15
@FrozenPandaz FrozenPandaz force-pushed the fix/windows-hide-true branch from 3c14422 to 3fb67b5 Compare March 19, 2026 21:15
@github-actions
Copy link
Contributor

🐳 We have a release for that!

This PR has a release associated with it. You can try it out using this command:

npx create-nx-workspace@22.7.0-pr.34894.3fb67b5 my-workspace

Or just copy this version and use it in your own command:

22.7.0-pr.34894.3fb67b5
Release details 📑
Published version 22.7.0-pr.34894.3fb67b5
Triggered by @FrozenPandaz
Branch fix/windows-hide-true
Commit 3fb67b5
Workflow run 23321279603

To request a new release for this pull request, mention someone from the Nx team or the @nrwl/nx-pipelines-reviewers.

FrozenPandaz and others added 2 commits March 19, 2026 23:49
…X_USE_LOCAL for AI agents

- Add shared `create_command` and `create_shell_command` utils in
  native/utils/command.rs to centralize CREATE_NO_WINDOW on Windows
- Use `create_command` in ide/install.rs for code.cmd calls that
  detect/install Nx Console extensions from the daemon
- Consolidate hash_runtime.rs and machine_id/mod.rs to use the shared utils
- Respect NX_USE_LOCAL env var in AI agents status check to skip
  downloading nx@latest (which can open console windows on Windows)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…X_USE_LOCAL for AI agents

- Add shared `create_command` and `create_shell_command` utils in
  native/utils/command.rs to centralize CREATE_NO_WINDOW on Windows
- Use `create_command` in ide/install.rs for code.cmd calls that
  detect/install Nx Console extensions from the daemon
- Consolidate hash_runtime.rs and machine_id/mod.rs to use the shared utils
- Respect NX_USE_LOCAL env var in AI agents status check to skip
  downloading nx@latest (which can open console windows on Windows)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> [Self-Healing CI Rerun]
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 has identified a flaky task in your failed CI:

🔂 Since the failure was identified as flaky, we triggered a CI rerun by adding an empty commit to this branch.

Nx Cloud View detailed reasoning in Nx Cloud ↗

🔔 Heads up, your workspace has pending recommendations ↗ to auto-apply fixes for similar failures.


🎓 Learn more about Self-Healing CI on nx.dev

@FrozenPandaz FrozenPandaz merged commit c1a93cb into master Mar 20, 2026
23 checks passed
@FrozenPandaz FrozenPandaz deleted the fix/windows-hide-true branch March 20, 2026 13:29
FrozenPandaz added a commit that referenced this pull request Mar 20, 2026
## Current Behavior

On Windows, the Nx daemon runs as a detached background process with no
console. When child processes are spawned without `windowsHide: true`
(Node.js) or `CREATE_NO_WINDOW` (Rust/Win32), Windows allocates a new
visible console window for each subprocess. This causes command prompt
windows to flash on screen during:

- Project graph creation (daemon spawn, plugin workers)
- Task hashing (runtime hashers)
- NX Console extension detection (`code.cmd --list-extensions`, etc.)
- AI agent configuration checks (`git ls-remote`, npm install)
- Machine ID retrieval, package manager version detection, git
operations, and more

The issue is especially noticeable "after a little bit" following daemon
startup, because background operations like the NX Console status check
and AI agents configuration check kick off after the initial project
graph is computed.

## Expected Behavior

No console windows should flash on Windows. All child process spawns use
`windowsHide: true` (Node.js) or `CREATE_NO_WINDOW` (Rust) to suppress
console windows.

## Root Causes Found

Investigation using a child_process interceptor in the daemon revealed
multiple sources:

1. **Rust native `ide/install.rs`** — `Command::new("code.cmd")` calls
for NX Console extension detection/installation were missing
`CREATE_NO_WINDOW`
2. **Rust native `hash_runtime.rs`** — Had its own `CREATE_NO_WINDOW`
handling but was duplicated
3. **`nx@latest` temp install** — The daemon downloads `nx@latest` to a
temp directory for NX Console and AI agent checks. The install process
(`pnpm add -D nx@latest`) and the downloaded code's `git ls-remote`
calls run without `windowsHide: true`
4. **~120 Node.js `child_process` calls** — Various
`spawn`/`exec`/`execSync` calls across the codebase were missing
`windowsHide: true`

## Changes

### Node.js child_process fixes
- Set `windowsHide: true` on all `spawn`/`exec`/`execSync`/`spawnSync`
calls across the codebase (~120 files)
- Added custom ESLint rule `@nx/workspace/require-windows-hide` that
errors when any spawn/exec call is missing `windowsHide: true`

### Rust native fixes
- **New shared util `native/utils/command.rs`** with `create_command()`
and `create_shell_command()` that set `CREATE_NO_WINDOW` on Windows —
centralizes the pattern so future Rust code gets it right by default
- **`ide/install.rs`** — Use `create_command()` for `code.cmd` calls
(list-extensions, install-extension, version check)
- **`hash_runtime.rs`** — Replaced local `create_command_builder()` with
shared `create_shell_command()`
- **`machine_id/mod.rs`** — Updated to use shared
`create_shell_command()`

### Daemon background operation fixes
- **`handle-configure-ai-agents.ts`** — Now respects `NX_USE_LOCAL` env
var to skip downloading `nx@latest`, avoiding the pnpm install that
opens windows. Once these fixes ship in a release, the downloaded
`nx@latest` will also have the fixes.

### Inlined node-machine-id
- Replaced the `node-machine-id` npm package with an inlined
implementation in `machine-id-cache.ts` that uses `windowsHide: true`
- The original package used `exec`/`execSync` without `windowsHide`

## Related Issue(s)

Supersedes #34455

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.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.

2 participants