Skip to content

fix: resolve Claude CLI detection failure when Node.js installed on non-C drive#1965

Closed
cpc5622 wants to merge 2 commits intoAndyMik90:developfrom
cpc5622:fix/claude-detection-windows-nodejs-path
Closed

fix: resolve Claude CLI detection failure when Node.js installed on non-C drive#1965
cpc5622 wants to merge 2 commits intoAndyMik90:developfrom
cpc5622:fix/claude-detection-windows-nodejs-path

Conversation

@cpc5622
Copy link

@cpc5622 cpc5622 commented Mar 17, 2026

Summary

  • env-utils: on Windows, add process.execPath directory to the augmented PATH so node.exe is always resolvable by subprocesses, regardless of which drive or path Node.js is installed on
  • cli-tool-manager: when validating a .cmd file, also add the parent of cmdDir (e.g. D:\NodeJs\ when cmd is in D:\NodeJs\node_global\) to cover npm global prefix layouts where node.exe lives one level up

Root Cause

When Electron launches from the GUI (Start Menu / taskbar), it does not inherit the full Windows system PATH. The hardcoded fallback paths in COMMON_BIN_PATHS.win32 only cover standard C:\Program Files\nodejs locations.

If a user installs Node.js on a non-C drive (e.g. D:\NodeJs\), the detection chain works as follows:

  1. where.exe successfully finds D:\NodeJs\node_global\claude.cmd
  2. validateClaude() runs cmd.exe /d /s /c "claude.cmd" --version
  3. claude.cmd internally calls node — but D:\NodeJs\ is not in the subprocess PATH
  4. 'node' is not recognized as an internal or external command (shows as mojibake due to GBK→UTF-8 encoding mismatch in the console)
  5. validateClaude returns valid: false → detection falls through to fallback / not found

Test plan

  • npm run lint — no errors (warnings are pre-existing)
  • npm run typecheck — passes
  • npm test — 1 pre-existing failure in phase-config.test.ts unrelated to this change (confirmed by running tests on unmodified develop branch)
  • Locally reproduced: Claude CLI at D:\NodeJs\node_global\claude.cmd now detected correctly after this fix

AI Assistance

This PR was built with AI assistance (Claude). The contributor has reviewed and understands the changes:

  • env-utils.ts: getExpandedPlatformPaths() now appends path.dirname(process.execPath) on win32 — the directory of the running Node.js/Electron binary, guaranteed to contain node.exe
  • cli-tool-manager.ts: validateClaude() and validateClaudeAsync() now pass both cmdDir and path.dirname(cmdDir) as extra PATH entries when validating .cmd files

Testing level: Lightly tested — lint/typecheck pass, local Windows reproduction confirmed.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed an issue where the PATH environment variable could be missing on Windows when launching the desktop app from the GUI.

…on-C drive

When Electron launches from GUI (Start Menu/taskbar), it does not inherit
the full Windows system PATH. This caused claude.cmd validation to fail
with 'node is not recognized' for custom Node.js install locations
(e.g. D:\NodeJs\) because only standard C: drive paths were in the
augmented PATH.

Two complementary fixes:
- env-utils: add process.execPath directory to augmented PATH on Windows
  so node.exe is always resolvable by subprocesses, regardless of which
  drive or path Node.js is installed on
- cli-tool-manager: when validating a .cmd file, also add the parent of
  cmdDir (e.g. D:\NodeJs\ when cmd is in D:\NodeJs\node_global\) to cover
  npm global prefix layouts where node.exe lives one level up
@CLAassistant
Copy link

CLAassistant commented Mar 17, 2026

CLA assistant check
All committers have signed the CLA.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical issue preventing the Claude CLI from being detected on Windows systems where Node.js is installed on a drive other than C:, particularly when the Electron application is launched from the GUI. The changes ensure that subprocesses can correctly locate the node.exe executable by explicitly augmenting the system PATH with relevant Node.js installation directories, thereby resolving the "node is not recognized" error.

Highlights

  • Windows PATH Resolution: Enhanced PATH resolution on Windows by adding the process.execPath directory to the augmented PATH, ensuring node.exe is always discoverable by subprocesses, especially when Node.js is installed on a non-C drive.
  • CLI Tool Validation: Improved .cmd file validation by including the parent directory of the command's location in the PATH, addressing scenarios where node.exe resides one level up from the npm global prefix.
Changelog
  • apps/desktop/src/main/cli-tool-manager.ts
    • Modified validateClaude to include the parent directory of the command's location in the augmented PATH for .cmd files.
    • Modified validateClaudeAsync to include the parent directory of the command's location in the augmented PATH for .cmd files.
  • apps/desktop/src/main/env-utils.ts
    • Added the directory containing process.execPath to the expanded platform paths on Windows.
Activity
  • No human activity has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: cb9ff08d-a606-4819-bd2d-7cfc73cd3b69

📥 Commits

Reviewing files that changed from the base of the PR and between 9d1e7a1 and 80a29d1.

📒 Files selected for processing (1)
  • apps/desktop/src/main/env-utils.ts

📝 Walkthrough

Walkthrough

Added PATH environment variable key normalization for Windows GUI launches. The change imports a normalization utility and calls it before PATH augmentation in both synchronous and asynchronous methods to prevent PATH omissions.

Changes

Cohort / File(s) Summary
PATH Normalization for Windows GUI Launches
apps/desktop/src/main/env-utils.ts
Added import of normalizeEnvPathKey utility and integrated calls in getAugmentedEnv and getAugmentedEnvAsync functions to normalize Windows PATH environment variable keys before augmentation. Includes clarifying comments on Windows-specific behavior.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A rabbit hops through Windows paths,
Normalizing keys with gentle laughs,
GUI launches now won't lose their way,
PATH stays true, hip-hip-hooray! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: resolving Claude CLI detection failure when Node.js is installed on non-C drives on Windows by normalizing the PATH environment variable.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can generate a title for your PR based on the changes with custom instructions.

Set the reviews.auto_title_instructions setting to generate a title for your PR based on the changes in the PR with custom instructions.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

🎉 Thanks for your first PR!

A maintainer will review it soon. Please make sure:

  • Your branch is synced with develop
  • CI checks pass
  • You've followed our contribution guide

Welcome to the Auto Claude community!

Copy link
Contributor

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

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 effectively resolves the Claude CLI detection failure on Windows when Node.js is installed on a non-C drive. The changes are well-reasoned and address the issue from two angles: a general enhancement in env-utils.ts to make node.exe more discoverable, and a specific fix in cli-tool-manager.ts to handle common npm global installation layouts. The code is clear and the comments are helpful. I've left a few suggestions to refactor some duplicated logic and simplify the code for improved maintainability.

Comment on lines +1106 to +1113
const extraPaths: string[] = [];
if (cmdDir && cmdDir !== '.') {
extraPaths.push(cmdDir);
const nodeParentDir = path.dirname(cmdDir);
if (nodeParentDir && nodeParentDir !== cmdDir) {
extraPaths.push(nodeParentDir);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This logic for determining extra paths is duplicated in validateClaudeAsync on lines 1280-1287. To improve maintainability and follow the DRY (Don't Repeat Yourself) principle, consider extracting this logic into a private helper method within the CLIToolManager class.

For example, you could add a method like this:

private _getExtraPathsForCmd(cmdDir: string): string[] {
  const extraPaths: string[] = [];
  if (cmdDir && cmdDir !== '.') {
    extraPaths.push(cmdDir);
    const nodeParentDir = path.dirname(cmdDir);
    if (nodeParentDir && nodeParentDir !== cmdDir) {
      extraPaths.push(nodeParentDir);
    }
  }
  return extraPaths;
}

You could then call this helper in both validateClaude and validateClaudeAsync.

extraPaths.push(nodeParentDir);
}
}
const env = getAugmentedEnv(extraPaths.length > 0 ? extraPaths : []);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This ternary expression is redundant. The getAugmentedEnv function can handle an empty array, so you can pass extraPaths directly.

Suggested change
const env = getAugmentedEnv(extraPaths.length > 0 ? extraPaths : []);
const env = getAugmentedEnv(extraPaths);

extraPaths.push(nodeParentDir);
}
}
const env = await getAugmentedEnvAsync(extraPaths.length > 0 ? extraPaths : []);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This ternary expression is redundant. The getAugmentedEnvAsync function can handle an empty array, so you can pass extraPaths directly.

Suggested change
const env = await getAugmentedEnvAsync(extraPaths.length > 0 ? extraPaths : []);
const env = await getAugmentedEnvAsync(extraPaths);

@cpc5622
Copy link
Author

cpc5622 commented Mar 17, 2026

I have read the CLA Document and I hereby sign the CLA

Comment on lines +184 to +188
if (platform === 'win32') {
const nodeDir = path.dirname(process.execPath);
if (nodeDir && !expandedPaths.includes(nodeDir)) {
expandedPaths.push(nodeDir);
}

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/main/env-utils.ts`:
- Around line 184-189: Replace the direct process.platform check with the
existing platform helper: use isWindows() instead of comparing platform ===
'win32' in the block that mutates expandedPaths; locate the conditional around
nodeDir and expandedPaths in env-utils.ts and change the condition to if
(isWindows()) while leaving the inner logic (const nodeDir =
path.dirname(process.execPath); if (nodeDir && !expandedPaths.includes(nodeDir))
{ expandedPaths.push(nodeDir); }) unchanged so the platform abstraction is
consistently used.
- Around line 179-189: The Windows-specific PATH tweak currently uses
process.execPath and platform === 'win32' and can incorrectly add the packaged
Electron install dir to expandedPaths; change the condition to use the
isWindows() helper and only run the node-dir addition when the app is not
packaged (use !app.isPackaged). Import app from 'electron' if not already
imported, compute nodeDir from process.execPath and push it into expandedPaths
only when isWindows() && !app.isPackaged and nodeDir is non-empty and not
already present. Ensure you still reference expandedPaths and process.execPath
exactly as in the diff.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6c9f3b34-a8cc-439f-bfdd-46b66a9f8b5c

📥 Commits

Reviewing files that changed from the base of the PR and between 979d977 and 9d1e7a1.

📒 Files selected for processing (2)
  • apps/desktop/src/main/cli-tool-manager.ts
  • apps/desktop/src/main/env-utils.ts

Comment on lines +179 to +189
// On Windows, add the directory of the current Node.js executable so that
// subprocesses (e.g. claude.cmd) can find `node` even when Electron was
// launched from the GUI and didn't inherit the full system PATH.
// process.execPath points to the Electron binary, but in packaged apps the
// real node.exe lives alongside it; in dev it's the node binary itself.
if (platform === 'win32') {
const nodeDir = path.dirname(process.execPath);
if (nodeDir && !expandedPaths.includes(nodeDir)) {
expandedPaths.push(nodeDir);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify how process.execPath is used elsewhere and whether app.isPackaged guards exist

echo "=== Checking process.execPath usage in the codebase ==="
rg -n "process\.execPath" --type ts -C 3

echo ""
echo "=== Checking if app.isPackaged is used alongside process.execPath ==="
rg -n "app\.isPackaged" --type ts -A 5 -B 2 | head -100

Repository: AndyMik90/Aperant

Length of output: 9594


🏁 Script executed:

#!/bin/bash
# Get full context of env-utils.ts to understand the function
echo "=== Full context of env-utils.ts lines 170-195 ==="
sed -n '170,195p' apps/desktop/src/main/env-utils.ts

echo ""
echo "=== Check what platform variable comes from ==="
head -50 apps/desktop/src/main/env-utils.ts

echo ""
echo "=== Search for cli-tool-manager.ts ==="
find . -name "cli-tool-manager.ts" -type f

Repository: AndyMik90/Aperant

Length of output: 3169


🏁 Script executed:

#!/bin/bash
# Look for the companion change mentioned in cli-tool-manager.ts
echo "=== Checking cli-tool-manager.ts for companion changes ==="
rg -n "claude.cmd\|node.exe\|\.cmd" apps/desktop/src/main/cli-tool-manager.ts -A 3 -B 3

echo ""
echo "=== Check how pty-daemon-client.ts uses process.execPath ==="
sed -n '125,140p' apps/desktop/src/main/terminal/pty-daemon-client.ts

Repository: AndyMik90/Aperant

Length of output: 694


🏁 Script executed:

#!/bin/bash
# Check if there's any bundling of node.exe in Electron package
echo "=== Search for references to node executable bundling ==="
rg -n "node\.exe|node binary|bundle.*node" --type ts --type md -i

echo ""
echo "=== Check electron-builder or packaging config ==="
find . -name "electron-builder.yml" -o -name "electron-builder.json" -o -name "*.electron-builder.*" -o -name "forge.config.*" 2>/dev/null | head -10

Repository: AndyMik90/Aperant

Length of output: 879


🏁 Script executed:

#!/bin/bash
# Get full function signature and usage context
echo "=== Function containing this code ==="
sed -n '140,200p' apps/desktop/src/main/env-utils.ts

echo ""
echo "=== Check if this function has app.isPackaged parameter ==="
rg -B 10 "platform === 'win32'" apps/desktop/src/main/env-utils.ts | head -30

Repository: AndyMik90/Aperant

Length of output: 3152


🏁 Script executed:

#!/bin/bash
# Check how app.isPackaged is imported in this file
echo "=== Imports in env-utils.ts ==="
head -20 apps/desktop/src/main/env-utils.ts

echo ""
echo "=== Check where this function is called from ==="
rg -n "getChildProcessEnv\|expandPaths" apps/desktop/src/main/ --type ts

Repository: AndyMik90/Aperant

Length of output: 911


Guard process.execPath addition with !app.isPackaged and use platform abstraction function.

The comment's claim that "in packaged apps the real node.exe lives alongside [the Electron binary]" is inaccurate. In packaged Electron, process.execPath points to the Electron executable, not Node.js, and its directory doesn't contain node.exe. This code will add the Electron app's installation directory to PATH in production, which won't resolve node.exe for .cmd scripts.

Additionally, the code uses process.platform directly instead of the isWindows() helper already imported from ./platform (per cross-platform guidelines).

Wrap the Windows-specific PATH manipulation with !app.isPackaged so it only applies in development where process.execPath is actually the Node.js binary. Import app from 'electron' if needed. Also replace if (platform === 'win32') with if (isWindows()).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/env-utils.ts` around lines 179 - 189, The
Windows-specific PATH tweak currently uses process.execPath and platform ===
'win32' and can incorrectly add the packaged Electron install dir to
expandedPaths; change the condition to use the isWindows() helper and only run
the node-dir addition when the app is not packaged (use !app.isPackaged). Import
app from 'electron' if not already imported, compute nodeDir from
process.execPath and push it into expandedPaths only when isWindows() &&
!app.isPackaged and nodeDir is non-empty and not already present. Ensure you
still reference expandedPaths and process.execPath exactly as in the diff.

Comment on lines +184 to +189
if (platform === 'win32') {
const nodeDir = path.dirname(process.execPath);
if (nodeDir && !expandedPaths.includes(nodeDir)) {
expandedPaths.push(nodeDir);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Guideline violation: Use isWindows() instead of process.platform check.

Line 170 reads process.platform directly and line 184 compares against 'win32'. The coding guidelines require using platform abstraction functions from ./platform/. Since isWindows is already imported on line 19, use it here for consistency.

🔧 Suggested fix
-  if (platform === 'win32') {
+  if (isWindows()) {
     const nodeDir = path.dirname(process.execPath);
     if (nodeDir && !expandedPaths.includes(nodeDir)) {
       expandedPaths.push(nodeDir);
     }
   }

As per coding guidelines: "Never use process.platform directly. Import platform detection from apps/desktop/src/main/platform/ instead."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (platform === 'win32') {
const nodeDir = path.dirname(process.execPath);
if (nodeDir && !expandedPaths.includes(nodeDir)) {
expandedPaths.push(nodeDir);
}
}
if (isWindows()) {
const nodeDir = path.dirname(process.execPath);
if (nodeDir && !expandedPaths.includes(nodeDir)) {
expandedPaths.push(nodeDir);
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/env-utils.ts` around lines 184 - 189, Replace the
direct process.platform check with the existing platform helper: use isWindows()
instead of comparing platform === 'win32' in the block that mutates
expandedPaths; locate the conditional around nodeDir and expandedPaths in
env-utils.ts and change the condition to if (isWindows()) while leaving the
inner logic (const nodeDir = path.dirname(process.execPath); if (nodeDir &&
!expandedPaths.includes(nodeDir)) { expandedPaths.push(nodeDir); }) unchanged so
the platform abstraction is consistently used.

On Windows, spreading process.env produces a plain object with the
native key casing 'Path' (not 'PATH'). getAugmentedEnv() accessed
env.PATH which was undefined, causing the entire original system PATH
to be silently dropped. The function then set a new 'PATH' key with
only COMMON_BIN_PATHS entries, creating dual keys ('Path' + 'PATH')
where the augmented-only value overwrote the original in subprocesses.

This caused tools installed on non-C drives (e.g. D:\NodeJs\) to be
unfindable by subprocesses, even though the paths were correctly
configured in the system environment.

Fix: call normalizeEnvPathKey() (already exists in agent/env-utils.ts)
right after spreading process.env in both getAugmentedEnv() and
getAugmentedEnvAsync(), ensuring env.PATH correctly reads the original
system PATH before augmentation.

Also reverts the previous workaround in cli-tool-manager.ts (parent-dir
heuristic) as it is no longer needed with the root cause fixed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cpc5622
Copy link
Author

cpc5622 commented Mar 17, 2026

Closing this PR. The root cause was identified as a Path vs PATH casing issue in getAugmentedEnv() — will resubmit with a cleaner fix.

@cpc5622 cpc5622 closed this Mar 17, 2026
@cpc5622 cpc5622 deleted the fix/claude-detection-windows-nodejs-path branch March 17, 2026 06:36
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