Skip to content

Commit 1fac893

Browse files
FrozenPandazclaudenx-cloud[bot]
committed
fix(core): set windowsHide: true on all child process spawns (#34894)
## 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>
1 parent 7c9de13 commit 1fac893

File tree

109 files changed

+536
-204
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+536
-204
lines changed

.eslintrc.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"env": {
55
"node": true
66
},
7-
"ignorePatterns": ["**/*.ts", "**/test-output"],
7+
"ignorePatterns": ["**/*.ts", "**/test-output", "**/dist"],
88
"plugins": ["@typescript-eslint", "@nx"],
99
"extends": ["plugin:storybook/recommended"],
1010
"rules": {
@@ -76,7 +76,8 @@
7676
]
7777
}
7878
],
79-
"@nx/workspace/valid-command-object": "error"
79+
"@nx/workspace/valid-command-object": "error",
80+
"@nx/workspace/require-windows-hide": "error"
8081
}
8182
},
8283
{

astro-docs/src/plugins/utils/nx-cli-generation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export async function loadNxCliPackage(
4343
cwd: workspaceRoot,
4444
silent: true,
4545
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
46+
windowsHide: true,
4647
});
4748

4849
child.on('message', (message: any) => {
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2-
"extends": "../../../../.eslintrc.json"
2+
"extends": "../../../../.eslintrc.json",
3+
"ignorePatterns": ["dist/**"]
34
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@
389389
"license-checker": "^25.0.1",
390390
"next": "14.2.35",
391391
"next-seo": "^5.13.0",
392-
"node-machine-id": "1.1.12",
392+
393393
"npm-run-path": "^4.0.1",
394394
"picomatch": "catalog:",
395395
"preact": "10.25.4",

packages/create-nx-workspace/src/utils/child-process-utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export function spawnAndWait(command: string, args: string[], cwd: string) {
2525
ESLINT_USE_FLAT_CONFIG: process.env.ESLINT_USE_FLAT_CONFIG ?? 'true',
2626
},
2727
shell: true,
28-
windowsHide: false,
28+
windowsHide: true,
2929
});
3030

3131
childProcess.on('exit', (code, signal) => {
@@ -50,7 +50,7 @@ export function execAndWait(
5050
{
5151
cwd,
5252
env: { ...process.env, NX_DAEMON: 'false' },
53-
windowsHide: false,
53+
windowsHide: true,
5454
maxBuffer: 1024 * 1024 * 10, // 10MB — default 1MB can be exceeded by verbose PM output
5555
},
5656
(error, stdout, stderr) => {

packages/create-nx-workspace/src/utils/git/default-base.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function deduceDefaultBase(): string {
88
const nxDefaultBase = 'main';
99
try {
1010
return (
11-
execSync('git config --get init.defaultBranch', { windowsHide: false })
11+
execSync('git config --get init.defaultBranch', { windowsHide: true })
1212
.toString()
1313
.trim() || nxDefaultBase
1414
);

packages/create-nx-workspace/src/utils/git/git.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export async function checkGitVersion(): Promise<string | null | undefined> {
3636
*/
3737
export function isGitAvailable(): boolean {
3838
try {
39-
execSync('git --version', { stdio: 'ignore' });
39+
execSync('git --version', { stdio: 'ignore', windowsHide: true });
4040
return true;
4141
} catch {
4242
return false;
@@ -49,7 +49,7 @@ export function isGitAvailable(): boolean {
4949
*/
5050
export function isGhCliAvailable(): boolean {
5151
try {
52-
execSync('gh --version', { stdio: 'ignore' });
52+
execSync('gh --version', { stdio: 'ignore', windowsHide: true });
5353
return true;
5454
} catch {
5555
return false;

packages/create-nx-workspace/src/utils/nx/ab-testing.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ function shouldRecordStats(): boolean {
370370
// Use npm to check registry - this works regardless of which package manager invoked us
371371
const stdout = execSync('npm config get registry', {
372372
encoding: 'utf-8',
373-
windowsHide: false,
373+
windowsHide: true,
374374
});
375375
const url = new URL(stdout.trim());
376376

packages/create-nx-workspace/src/utils/package-manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ export function getPackageManagerVersion(
266266
const version = execSync(`${packageManager} --version`, {
267267
cwd,
268268
encoding: 'utf-8',
269-
windowsHide: false,
269+
windowsHide: true,
270270
}).trim();
271271
pmVersionCache.set(packageManager, version);
272272
return version;

packages/cypress/plugins/cypress-preset.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,14 @@ function startWebServer(webServerCommand: string) {
7777
// Windows is fine so we leave it attached to this process
7878
detached: process.platform !== 'win32',
7979
stdio: 'inherit',
80-
windowsHide: false,
80+
windowsHide: true,
8181
});
8282

8383
return async () => {
8484
if (process.platform === 'win32') {
8585
try {
8686
execSync('taskkill /pid ' + serverProcess.pid + ' /T /F', {
87-
windowsHide: false,
87+
windowsHide: true,
8888
});
8989
} catch (e) {
9090
if (process.env.NX_VERBOSE_LOGGING === 'true') {

0 commit comments

Comments
 (0)