fix(openclaw): fix Node.js detection for nvm/mise/fnm-managed installations#12902
Merged
kangfenmao merged 13 commits intomainfrom Feb 14, 2026
Merged
fix(openclaw): fix Node.js detection for nvm/mise/fnm-managed installations#12902kangfenmao merged 13 commits intomainfrom
kangfenmao merged 13 commits intomainfrom
Conversation
…ations When Node.js is installed via version managers (nvm, mise, fnm) after the app has started, OpenClaw fails to detect it because the cached shell environment is stale. This commit refactors the shell environment and process utilities to fix the issue: - Separate shell env cache into CQS pair: getShellEnv (query) vs refreshShellEnv (command) to make cache invalidation explicit - Remove hidden cache refresh side effect from findExecutableInEnv - Fix startGateway to use refreshed env consistently - Add checkNodeVersion with discriminated union return type - Add node/git version polling and download URL hints in OpenClaw UI - Unify spawn strategy: replace spawn()+shell:true with crossPlatformSpawn - Fix cache mutation bug: clone env before PATH augmentation in install() - Rename spawnWithEnv→crossPlatformSpawn, executeInEnv→executeCommand Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace manual parseInt-based version parsing with semver.coerce + semver.lt for more robust version comparison. semver is already a project dependency used in AppUpdater.ts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ess env On Windows, `cmd.exe /c set` just inherits the parent Electron process's env, so PATH changes (e.g. Node.js installed via MSI after app launch) are never picked up. Replace the shell spawn with direct registry reads of HKLM and HKCU PATH values, expanding %VAR% references in-place. This fixes npm preinstall failures where `node` can't be found despite being installed, because the captured PATH was stale. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cover the key scenarios: - stale PATH replaced by fresh registry value - system + user PATH combined - fallback when registry is unavailable - %VAR% expansion (case-insensitive, unknown vars preserved) - REG_SZ vs REG_EXPAND_SZ handling - Cherry Studio bin appended - no cmd.exe shell spawned on Windows Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Change findExecutable default extensions from ['.exe'] to ['.exe', '.cmd'] since .cmd is the standard npm shim format on Windows - Remove commonPaths from FindExecutableOptions — redundant now that getShellEnv() reads fresh PATH from the Windows registry - Remove options parameter from findExecutableInEnv entirely — callers no longer need to pass extensions or commonPaths - Simplify findNpmPath and findOpenClawBinary call sites Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Inline findNpmPath into its two call sites (install, uninstall) - Remove possiblePaths filesystem fallback from findOpenClawBinary — openclaw is installed via npm global, so it's always in PATH - findOpenClawBinary now delegates entirely to findExecutableInEnv Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fe97be7 to
44cf95e
Compare
The clone-and-augment pattern was needed when getShellEnv() returned stale PATH. Now that Windows reads fresh PATH from the registry and Unix reads from the login shell, the node/git directories are already in PATH. No need to clone the env or manually prepend directories. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ormSpawn quoting
- Change findExecutableInEnv to return `string | null` instead of
`{ path, env }` — the returned env was redundant since callers either
ignored it or obtained it separately via getShellEnv()/refreshShellEnv(),
and executeCommand already defaults to getShellEnv().
- Fix crossPlatformSpawn to use `shell: true` for .cmd files instead of
manually constructing `cmd.exe /c` args. The manual approach breaks when
both the command path and arguments contain spaces (cmd.exe's
quote-stripping rule 2 mangles the command line).
- Remove env parameter threading from PluginService.cloneRepository and
resolveDefaultBranch since executeCommand handles it internally.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract NodeCheckResult from OpenClawService.ts to packages/shared/config/types.ts so it can be shared across the main process, preload bridge, and renderer without duplication. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…version requirements in multiple languages - Translated "Export to Excel" and related success/error messages in various languages. - Updated Node.js version requirement messages to provide clearer instructions in the respective languages.
kangfenmao
reviewed
Feb 14, 2026
| @@ -243,7 +243,7 @@ function normalizeVersion(tag: string): string { | |||
| } | |||
|
|
|||
| function detectChannel(version: string): UpgradeChannel | null { | |||
Collaborator
There was a problem hiding this comment.
这个改动是否和本次提交有关,另外修改此处会影响应用升级吗
# Conflicts: # src/renderer/src/i18n/translate/de-de.json # src/renderer/src/i18n/translate/ro-ro.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this PR does
Before this PR:
getLoginShellEnvironment()runscmd.exe /c setwhich just inherits the parent (Electron) process's env — it does NOT re-read the Windows registry. If Node.js is installed via MSI after the app launches, the captured PATH is stale. This causes npm preinstall scripts to fail: npm itself runs (found viacommonPathsfilesystem fallback), butcmd.exe /d /s /c node ./engine-requirements.jscan't findnodein the stale PATH. On Unix this isn't an issue becausezsh -ilc envsources profile files and picks up nvm/mise/fnm PATH changes.findExecutableInEnvhas a hidden side effect of refreshing the shell env cache, making it unpredictable.startGatewayuses stale env becausefindOpenClawBinaryrefreshes the cache but the gateway spawn uses a different (stale) env.spawn()+shell: truewhile gateway operations usespawnWithEnv().After this PR:
cmd.exe /c setentirely. Instead, copyprocess.envas the base (same result, but faster), then read the current system + user PATH from the Windows registry viareg query, expand%VAR%references, and replace the stale PATH. This ensures newly installed tools (e.g. Node.js MSI) are found immediately.zsh -ilc envstill sources profile files correctly.getShellEnv()is a pure query,refreshShellEnv()is an explicit command.findExecutableInEnvno longer refreshes the cache — callers explicitly callrefreshShellEnv()when they need fresh env.startGatewayrefreshes env first, then passes it to bothfindOpenClawBinaryandcrossPlatformSpawn.{ ...await getShellEnv() }).crossPlatformSpawn(handles Windows.cmdfiles viacmd.exe /c).Why we need it and why it was done in this way
Why registry reads instead of
cmd.exe /c set?On Windows,
cmd.exe /c setinherits the parent process env unchanged. Unlike Unix shells that source~/.bashrc/.zshrcon launch,cmd.exedoes not re-read the registry. When a user installs Node.js (via MSI, Scoop, etc.) after Cherry Studio is already running, the new PATH entries only exist in the registry — not in the Electron process's inherited env. ReadingHKLM\...\Environment(system PATH) andHKCU\Environment(user PATH) directly gives us the ground-truth PATH at the time of the call.Why
execFileSyncinstead ofcrossPlatformSpawn/executeCommand?executeCommandinternally callsgetShellEnv()to obtain env. SincequeryRegValueis called bygetShellEnv→getLoginShellEnvironment→getWindowsEnvironment, usingexecuteCommandwould create an infinite recursion.reg querycompletes in milliseconds. Keeping it synchronous allowsgetWindowsEnvironment()to return directly viaPromise.resolve(), simplifying the control flow..cmdshim handling needed:reg.exeis a native executable — it doesn't need thecmd.exe /cwrapping thatcrossPlatformSpawnprovides.execFileSyncexecutes the binary directly without shell interpolation, avoiding command injection risk.Why expand
%VAR%manually?Windows registry stores PATH as
REG_EXPAND_SZwith embedded references like%SystemRoot%\system32. Thereg queryoutput returns the raw string without expansion. We expand these references against the currentprocess.envusing case-insensitive lookup to match Windows behavior.The following tradeoffs were made:
refreshShellEnv()beforefindExecutableInEnv()when they need fresh env. This adds a line of code at call sites but makes the caching behavior predictable and eliminates hidden side effects.install()rather thanObject.freeze()the cache, because freeze would break callers that legitimately need to add env vars (e.g.,OPENCLAW_CONFIG_PATH).The following alternatives were considered:
getShellEnv()always return a frozen copy — rejected because it would require all callers to spread, even those that only read.[Environment]::GetEnvironmentVariableinstead ofreg query— rejected because it has a much higher startup cost (~200ms vs ~5ms) and requires detecting PowerShell availability.Links to places where the discussion took place: N/A
Breaking changes
None. All changes are internal to the main process. No Redux/IndexedDB schema changes.
Special notes for your reviewer
src/main/utils/__tests__/shell-env.test.ts— 10 test cases covering registry PATH resolution (stale replacement, system+user combination,%VAR%expansion, REG_SZ vs REG_EXPAND_SZ, fallback behavior, cherry bin append, no cmd.exe spawn).shell-env.ts:queryRegValue(),expandWindowsEnvVars(),readWindowsRegistryPath(),getWindowsEnvironment()— all private, tested through the publicrefreshShellEnv()API.spawnWithEnv→crossPlatformSpawn,executeInEnv→executeCommand— names now reflect actual responsibility (Windows.cmdadaptation, not "env injection").checkNodeVersionreturns a discriminated union{ status: 'not_found' } | { status: 'version_low'; version; path } | { status: 'ok'; version; path }instead of the previouscheckNpmAvailableboolean.openclaw.node_required.*→openclaw.node_missing.*/openclaw.node_version_low.*with translations for all supported locales.Checklist
Release note
🤖 Generated with Claude Code