fix(core): set windowsHide: true on all child process spawns#34894
fix(core): set windowsHide: true on all child process spawns#34894FrozenPandaz merged 9 commits intomasterfrom
Conversation
✅ Deploy Preview for nx-dev ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for nx-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
View your CI Pipeline Execution ↗ for commit 827890d
☁️ 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 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.
| 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
Is this helpful? React 👍 or 👎 to let us know.
3b101a0 to
020ab80
Compare
| const hasNonLiteralObject = node.arguments.some( | ||
| (arg) => | ||
| arg.type === 'Identifier' || | ||
| arg.type === 'SpreadElement' || | ||
| (arg.type === 'ObjectExpression' && | ||
| arg.properties.some((p) => p.type === 'SpreadElement')) | ||
| ); |
There was a problem hiding this comment.
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 IdentifierThe 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;
}| 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
Is this helpful? React 👍 or 👎 to let us know.
020ab80 to
55db83d
Compare
|
Failed to publish a PR release of this pull request, triggered by @FrozenPandaz. |
690997e to
dac84ab
Compare
|
Failed to publish a PR release of this pull request, triggered by @FrozenPandaz. |
4924d24 to
b6f7688
Compare
dee4348 to
f872491
Compare
🐳 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-workspaceOr just copy this version and use it in your own command: 22.6.0-pr.34894.f872491
To request a new release for this pull request, mention someone from the Nx team or the |
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.
Co-authored-by: FrozenPandaz <FrozenPandaz@users.noreply.github.com>
3c14422 to
3fb67b5
Compare
🐳 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-workspaceOr just copy this version and use it in your own command: 22.7.0-pr.34894.3fb67b5
To request a new release for this pull request, mention someone from the Nx team or the |
…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]
There was a problem hiding this comment.
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.
🔔 Heads up, your workspace has pending recommendations ↗ to auto-apply fixes for similar failures.
🎓 Learn more about Self-Healing CI on nx.dev
## 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>
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) orCREATE_NO_WINDOW(Rust/Win32), Windows allocates a new visible console window for each subprocess. This causes command prompt windows to flash on screen during:code.cmd --list-extensions, etc.)git ls-remote, npm install)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) orCREATE_NO_WINDOW(Rust) to suppress console windows.Root Causes Found
Investigation using a child_process interceptor in the daemon revealed multiple sources:
ide/install.rs—Command::new("code.cmd")calls for NX Console extension detection/installation were missingCREATE_NO_WINDOWhash_runtime.rs— Had its ownCREATE_NO_WINDOWhandling but was duplicatednx@latesttemp install — The daemon downloadsnx@latestto a temp directory for NX Console and AI agent checks. The install process (pnpm add -D nx@latest) and the downloaded code'sgit ls-remotecalls run withoutwindowsHide: truechild_processcalls — Variousspawn/exec/execSynccalls across the codebase were missingwindowsHide: trueChanges
Node.js child_process fixes
windowsHide: trueon allspawn/exec/execSync/spawnSynccalls across the codebase (~120 files)@nx/workspace/require-windows-hidethat errors when any spawn/exec call is missingwindowsHide: trueRust native fixes
native/utils/command.rswithcreate_command()andcreate_shell_command()that setCREATE_NO_WINDOWon Windows — centralizes the pattern so future Rust code gets it right by defaultide/install.rs— Usecreate_command()forcode.cmdcalls (list-extensions, install-extension, version check)hash_runtime.rs— Replaced localcreate_command_builder()with sharedcreate_shell_command()machine_id/mod.rs— Updated to use sharedcreate_shell_command()Daemon background operation fixes
handle-configure-ai-agents.ts— Now respectsNX_USE_LOCALenv var to skip downloadingnx@latest, avoiding the pnpm install that opens windows. Once these fixes ship in a release, the downloadednx@latestwill also have the fixes.Inlined node-machine-id
node-machine-idnpm package with an inlined implementation inmachine-id-cache.tsthat useswindowsHide: trueexec/execSyncwithoutwindowsHideRelated Issue(s)
Supersedes #34455