Skip to content

fix: avoid cmd.exe spawn on Windows by using os.hostname() before execSync fallback - #7841

Merged
diegosouzapw merged 2 commits into
cheaper-inference:release/v3.8.49from
tientien17:fix/windows-hostname-exec-spawn
Jul 20, 2026
Merged

diegosouzapw merged 2 commits into
cheaper-inference:release/v3.8.49from
tientien17:fix/windows-hostname-exec-spawn

Conversation

@tientien17

Copy link
Copy Markdown
Contributor

Problem

On Windows, execSync() wraps the command in cmd.exe /d /s /c, spawning a new cmd.exe process. getMachineIdRaw() called execSync("hostname") as Strategy 4 before trying os.hostname() as Strategy 5. This meant:

  • Every server startup spawned a cmd.exe for the hostname call
  • Every dashboard API route that needed a machine ID spawned another cmd.exe
  • Users on Windows observed 8+ cmd.exe spawns during dashboard navigation

Fix

  1. Swapped strategy priority: os.hostname() (native Node.js binding, no child process) is now Strategy 4, tried BEFORE execSync("hostname") (Strategy 5)
  2. Added module-level caching: getMachineIdRaw() caches its result after the first successful call -- the machine ID never changes at runtime, so repeated API calls no longer re-query the OS

Verification

A runtime monkey-patch tracer confirmed execSync("hostname") was not called during startup or API requests after the fix on Windows Node.js 24.

Related

…cSync fallback

On Windows, execSync() wraps the command in cmd.exe /d /s /c,
spawning a new cmd.exe process. getMachineIdRaw() called
execSync("hostname") as Strategy 4 before trying os.hostname()
as Strategy 5 -- meaning every dashboard API call spawned an
unnecessary cmd.exe process.

This commit:
- Moves os.hostname() to Strategy 4 (no child process, native binding)
- Keeps execSync("hostname") as Strategy 5 fallback
- Adds module-level caching so getMachineIdRaw() only runs once per
  process lifetime since the machine ID never changes at runtime
- Caches all strategy results at the first successful return

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@tientien17
tientien17 requested a review from diegosouzapw as a code owner July 20, 2026 07:22

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a module-level cache ('cachedRawId') in 'src/shared/utils/machineId.ts' to avoid expensive shell executions on subsequent calls, and refactors the hostname fallback strategies to prioritize 'os.hostname()' over spawning a child process. The review feedback recommends exporting a cache-reset helper function to improve unit testability and reminds the author to add or update unit tests for these changes as required by the repository style guide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

* cmd.exe which is expensive. Cache the result after first call
* since the machine ID never changes at runtime.
*/
let cachedRawId: string | null = null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The introduction of the module-level cachedRawId variable makes it difficult to unit test different strategies or mock the environment across multiple test cases, as the cache cannot be cleared. To improve testability, consider exporting a helper function to reset the cache.

let cachedRawId: string | null = null;

/**
 * Resets the cached machine ID. Used primarily for testing.
 */
export function resetMachineIdCache(): void {
  cachedRawId = null;
}

Comment on lines 21 to +23
function getMachineIdRaw(): string {
// Return cached result immediately (machine identity is stable at runtime)
if (cachedRawId !== null) return cachedRawId;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

According to the Repository Style Guide (Rule 9 under Hard Rules), you must always include tests when changing production code under src/. Since src/shared/utils/machineId.ts has been modified, please add or update the corresponding unit tests in the tests/ directory to cover the new caching behavior and strategy fallback order.

References
  1. Always include tests when changing production code (src/, open-sse/, electron/, bin/). (link)

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Thanks for tracking this down — spawning cmd.exe on every dashboard API call is a real, annoying cost on Windows, and reordering os.hostname() ahead of execSync("hostname") plus caching the (stable) result is the right fix. I ran your change locally and confirmed:

  • The existing tests/unit/machine-id.test.ts suite still passes unmodified (5/5) — no regression in the Windows/macOS/Linux strategy fallback.
  • eslint on both touched files is clean, no any introduced.
  • The change merges cleanly onto the current release tip.

One thing that needs fixing before merge: running the new test file directly —

node --import tsx/esm --test tests/unit/shared/machineId.test.ts

— 2 of the 10 tests fail here (and would fail the same way on this repo's ubuntu-latest CI runners): "resetMachineIdCache clears cached value" and "os.hostname() (Strategy 4) is tried before execSync hostname (Strategy 5)". The root cause: disableWindowsRegistryStrategy() only disables Strategy 1 (Windows REG.exe); it doesn't touch Strategy 3 (/etc/machine-id), which exists and succeeds on real Linux boxes — including CI. So Strategy 3 wins before your mocked os.hostname() is ever reached, and both tests assert a value that never arrives.

Could you neutralize Strategy 3 the same way tests/unit/machine-id.test.ts already does (stub fs.readFileSync for /etc/machine-id / /var/lib/dbus/machine-id to throw)? Once those 2 tests are fixed, this looks ready to merge.

…cSync fallback

Prioritize os.hostname() (sync, no subprocess) over execSync hostname fallback. Cache the result so subsequent calls never spawn. Export resetMachineIdCache() for test isolation.

Tests: 10 tests covering cache behavior, strategy fallback order, and consistent machine ID hashing. The 2 tests that mock os.hostname() now also stub fs.readFileSync for /etc/machine-id to throw, so Strategy 3 (Linux machine-id file) does not preempt Strategy 4 on real Linux runners.
@tientien17
tientien17 force-pushed the fix/windows-hostname-exec-spawn branch from 5c5f6b5 to 1fe78bb Compare July 20, 2026 13:31
@tientien17

Copy link
Copy Markdown
Contributor Author

Fixed in 1fe78bb. disableWindowsRegistryStrategy() now also stubs fs.readFileSync for /etc/machine-id and /var/lib/dbus/machine-id to throw. All 10 new tests pass, all 5 existing tests pass.

@diegosouzapw
diegosouzapw merged commit 286628a into cheaper-inference:release/v3.8.49 Jul 20, 2026
3 checks passed
@diegosouzapw

Copy link
Copy Markdown
Collaborator

Merged — thanks @tientien17! os.hostname() before the execSync fallback kills a real per-call cmd.exe spawn, and you turned the hermetic-test feedback around fast. Validated in the local merge-train (14-PR combined tree, full unit suite + vitest green) on tomni-proxmox-113 @ 0fda37bb4fdb26ba1345fd7b4cac44fbe235cda4, then squash-merged.

@diegosouzapw diegosouzapw mentioned this pull request Jul 23, 2026
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…cSync fallback (cheaper-inference#7841)

* fix: avoid cmd.exe spawn on Windows by using os.hostname() before execSync fallback

On Windows, execSync() wraps the command in cmd.exe /d /s /c,
spawning a new cmd.exe process. getMachineIdRaw() called
execSync("hostname") as Strategy 4 before trying os.hostname()
as Strategy 5 -- meaning every dashboard API call spawned an
unnecessary cmd.exe process.

This commit:
- Moves os.hostname() to Strategy 4 (no child process, native binding)
- Keeps execSync("hostname") as Strategy 5 fallback
- Adds module-level caching so getMachineIdRaw() only runs once per
  process lifetime since the machine ID never changes at runtime
- Caches all strategy results at the first successful return

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: avoid cmd.exe spawn on Windows by using os.hostname() before execSync fallback

Prioritize os.hostname() (sync, no subprocess) over execSync hostname fallback. Cache the result so subsequent calls never spawn. Export resetMachineIdCache() for test isolation.

Tests: 10 tests covering cache behavior, strategy fallback order, and consistent machine ID hashing. The 2 tests that mock os.hostname() now also stub fs.readFileSync for /etc/machine-id to throw, so Strategy 3 (Linux machine-id file) does not preempt Strategy 4 on real Linux runners.

---------

Co-authored-by: tientien17 <tientien17@users.noreply.github.com>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…cSync fallback (cheaper-inference#7841)

* fix: avoid cmd.exe spawn on Windows by using os.hostname() before execSync fallback

On Windows, execSync() wraps the command in cmd.exe /d /s /c,
spawning a new cmd.exe process. getMachineIdRaw() called
execSync("hostname") as Strategy 4 before trying os.hostname()
as Strategy 5 -- meaning every dashboard API call spawned an
unnecessary cmd.exe process.

This commit:
- Moves os.hostname() to Strategy 4 (no child process, native binding)
- Keeps execSync("hostname") as Strategy 5 fallback
- Adds module-level caching so getMachineIdRaw() only runs once per
  process lifetime since the machine ID never changes at runtime
- Caches all strategy results at the first successful return

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix: avoid cmd.exe spawn on Windows by using os.hostname() before execSync fallback

Prioritize os.hostname() (sync, no subprocess) over execSync hostname fallback. Cache the result so subsequent calls never spawn. Export resetMachineIdCache() for test isolation.

Tests: 10 tests covering cache behavior, strategy fallback order, and consistent machine ID hashing. The 2 tests that mock os.hostname() now also stub fs.readFileSync for /etc/machine-id to throw, so Strategy 3 (Linux machine-id file) does not preempt Strategy 4 on real Linux runners.

---------

Co-authored-by: tientien17 <tientien17@users.noreply.github.com>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
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