fix(cli-claude): resolve npm global claude install via process.execPath#153
Conversation
The Agent SDK path resolver could not find Claude Code when it was installed via an npm global prefix whose `node` directory was not reliably present on PATH at runtime. This happens in two common setups: - Windows + nvm-windows, where `C:\Program Files\nodejs` is a junction to the active version and may be missing from a daemon-spawned child process's PATH. - Detached daemons on any platform whose PATH has been stripped down to a service-default set that does not include the npm global prefix (the same class of issue addressed by happier-dev#42 for systemd on Linux). In those environments every existing fallback (`resolveProviderCliCommand`, the versioned installer dir, the native installer locations) fails, and users see the confusing "Claude Code is not installed (or not detectable)" error even though `claude` works fine from their interactive shell. This change adds a new fallback step that derives the npm global `@anthropic-ai/claude-code/cli.js` entrypoint directly from `process.execPath`, which always points to the active `node` binary regardless of PATH state. Both the Windows layout (`<execDir>/node_modules/...`) and the Unix layout (`<execDir>/../lib/node_modules/...`) are checked. Includes unit tests that stub `process.execPath` to a sandbox directory and verify the new resolution path on both platform shapes. The existing tests are also updated to isolate `process.execPath` so the host's real npm global install cannot bleed into the assertions.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughAdds npm-global npm module discovery for Claude CLI entrypoints by deriving locations from dirname(process.execPath) and checking typical npm global layouts; integrates this probe into getDefaultClaudeCodePathForAgentSdk() between versioned-install detection and native-installer probing. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryThis PR adds Confidence Score: 5/5Safe to merge — the change is a narrowly scoped fallback resolution step with no effect on the existing happy paths. No P0 or P1 findings. The two remaining comments are P2 style suggestions (candidate ordering comment, fallback precedence comment). The core logic is correct for all documented npm layouts, tests cover both platforms, and the isolation strategy properly prevents host-environment bleed-through. No files require special attention.
|
| Filename | Overview |
|---|---|
| apps/cli/src/backends/claude/sdk/utils.ts | Adds findClaudeInNpmGlobalModules() helper and wires it into getDefaultClaudeCodePathForAgentSdk() as a fallback between PATH resolution and versioned install checks; logic is correct for both Unix and Windows npm layouts. |
| apps/cli/src/backends/claude/sdk/utils.test.ts | Adds stubExecPathToEmptySandbox() isolation helper to both describe blocks and two new tests covering Unix and Windows npm global resolution; restore/isolation lifecycle is correct. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[getDefaultClaudeCodePathForAgentSdk] --> B{HAPPIER_CLAUDE_PATH set?}
B -- yes --> C[Return override path]
B -- no --> D[resolveProviderCliCommand via PATH]
D -- found --> E[Return PATH-resolved command]
D -- not found --> F["findClaudeInNpmGlobalModules() NEW"]
F --> G{"Check execDir/node_modules/…\n(Windows layout)"}
G -- exists --> H[Return npm global cli.js]
G -- missing --> I{"Check execDir/../lib/node_modules/…\n(Unix layout)"}
I -- exists --> H
I -- missing --> J[findLatestVersionedClaudeEntrypointForAgentSdk]
J -- found --> K[Return versioned install]
J -- not found --> L[findClaudeInNativeInstallerLocations]
L -- found --> M[Return native installer path]
L -- not found --> N[Throw helpful error]
Reviews (1): Last reviewed commit: "fix(cli-claude): resolve npm global clau..." | Re-trigger Greptile
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/cli/src/backends/claude/sdk/utils.ts`:
- Around line 331-334: The npm-global fallback currently calls
canonicalizeClaudeEntrypointPath on npmGlobalPath which resolves
symlinks/junctions and pins to a versioned target; instead, when
findClaudeInNpmGlobalModules() returns a path and
isAgentSdkCompatibleClaudeEntrypoint(npmGlobalPath) passes, return npmGlobalPath
directly (preserving symlinks) rather than calling
canonicalizeClaudeEntrypointPath; update the branch that uses
findClaudeInNpmGlobalModules, isAgentSdkCompatibleClaudeEntrypoint, and
canonicalizeClaudeEntrypointPath to remove the canonicalization step so
PATH-style symlink updates remain effective.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9324e062-a6d7-42bd-b215-b9f712a6404a
📒 Files selected for processing (2)
apps/cli/src/backends/claude/sdk/utils.test.tsapps/cli/src/backends/claude/sdk/utils.ts
Addresses three review findings on happier-dev#153: - CodeRabbit (Major): drop canonicalizeClaudeEntrypointPath() on the npm-global fallback path. Canonicalization resolves symlinks and junctions (including nvm-windows C:\Program Files\nodejs) to their version-pinned targets, which breaks the "preserve symlinks so Claude auto-updates can retarget the same path" pattern that the PATH resolution branch already follows. - Greptile (P2): move the npm-global step to run AFTER the Unix versioned-install probe, so a user with both a newer ~/.local/share/claude/versions/ install and an older npm-global install still gets the versioned one. - Greptile (P2): strengthen the inline comment on the candidate array in findClaudeInNpmGlobalModules() to explicitly explain why checking the Windows candidate first is safe on Unix (that path layout never exists there). Tests: 15/15 passing. No behavior change in the PATH happy path or in the HAPPIER_CLAUDE_PATH override branch.
|
Thank you very much! :) Merged and will be pushed in next dev + preview release |
Summary
PATHto findclaudewhen it is installed via npm global. Daemons / spawned CLI processes frequently inherit a minimal PATH that does not contain the directory wherenodeitself lives, and the fallback paths (~/.local/bin, native installer locations) don't cover npm global installs either.Claude Code is not installed (or not detectable)error in the app whenclaudeworks fine from an interactive shell.C:\Program Files\nodejsis a junction managed by nvm and can be missing from a detached daemon child's PATH.happier daemon installtime and doesn't help CLIs spawned outside that flow.@anthropic-ai/claude-code/cli.jsentrypoint fromprocess.execPath, which always points to the active Node.js binary regardless of PATH state.Details
findClaudeInNpmGlobalModules()helper inapps/cli/src/backends/claude/sdk/utils.ts. It checks both the Windows layout (<execDir>/node_modules/...) and the Unix layout (<execDir>/../lib/node_modules/...).getDefaultClaudeCodePathForAgentSdk()as a new fallback step between the existingresolveProviderCliCommandattempt and the versioned install dir / native installer location checks. Ordering ensures the existing happy paths (PATH-resolvedclaude, explicitHAPPIER_CLAUDE_PATHoverride) are preserved and unchanged.getDefaultClaudeCodePathForAgentSdk(), which is called once per remote Claude session spawn.Test plan
apps/cli/src/backends/claude/sdk/utils.test.ts, stubbingprocess.execPathto a sandbox directory.stubExecPathToEmptySandbox()helper in bothdescribeblocks, so the "no Claude Code installed" assertions no longer accidentally find a realcli.json the test runner.vitest run src/backends/claude/sdk/utils.test.ts— 15/15 passing.process.execPathpoints atC:\Program Files\nodejs\node.exe, which resolves to the realC:\Program Files\nodejs\node_modules\@anthropic-ai\claude-code\cli.js.Related
Summary by CodeRabbit
New Features
Tests