Skip to content

Fix Claude Code CLI detection for npm-local installs#1978

Open
one-kash wants to merge 2 commits intogithub:mainfrom
one-kash:fix/claude-npm-local-detection
Open

Fix Claude Code CLI detection for npm-local installs#1978
one-kash wants to merge 2 commits intogithub:mainfrom
one-kash:fix/claude-npm-local-detection

Conversation

@one-kash
Copy link

Summary

  • Fixes specify check reporting "Claude Code CLI (not found)" for users with npm-local installs (the default Claude Code install method, common with nvm)
  • Adds CLAUDE_NPM_LOCAL_PATH (~/.claude/local/node_modules/.bin/claude) as a second well-known detection path alongside the existing migrate-installer path
  • Adds 8 unit tests covering all Claude CLI install methods (migrate-installer, npm-local, PATH) and non-Claude tools

Root Cause

check_tool("claude") only checks:

  1. ~/.claude/local/claude (migrate-installer path)
  2. shutil.which("claude") (system PATH)

For npm-local installs, the binary is at ~/.claude/local/node_modules/.bin/claude — neither of the above paths match, so detection fails even though Claude is installed and working.

Test plan

  • New test: detected via migrate-installer path (~/.claude/local/claude)
  • New test: detected via npm-local path (~/.claude/local/node_modules/.bin/claude)
  • New test: detected via system PATH
  • New test: correctly reports not found when genuinely missing
  • New test: StepTracker updated correctly for npm-local detection
  • New test: non-Claude tools unaffected (git, kiro-cli fallback)
  • Full test suite passes (847/847)

Fixes #550

`specify check` reports "Claude Code CLI (not found)" for users who
installed Claude Code via npm-local (the default installer path, common
with nvm). The binary lives at ~/.claude/local/node_modules/.bin/claude
which was not checked. Add CLAUDE_NPM_LOCAL_PATH as a second well-known
location alongside the existing migrate-installer path.

Fixes github#550
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes specify check incorrectly reporting Claude Code as missing when installed via npm-local by adding an additional well-known local binary path and covering the behavior with targeted tests.

Changes:

  • Add CLAUDE_NPM_LOCAL_PATH (~/.claude/local/node_modules/.bin/claude) and treat it as a valid Claude local install location in check_tool("claude").
  • Update Claude-specific detection logic comments to reflect multiple local install methods.
  • Add unit tests covering Claude detection via migrate-installer path, npm-local path, PATH, and tracker updates; plus regression checks for other tools.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/specify_cli/__init__.py Adds npm-local Claude binary path constant and checks it in check_tool before falling back to PATH.
tests/test_check_tool.py New tests validating Claude detection across install methods and ensuring non-Claude tools are unaffected.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +11 to +12
import pytest

Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

pytest is imported but not used in this test module (the tmp_path fixture doesn't require an explicit import). Consider removing the unused import to keep the tests clean.

Suggested change
import pytest

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +57
fake_missing = Path("/nonexistent/claude")

with patch("specify_cli.CLAUDE_LOCAL_PATH", fake_missing), \
patch("specify_cli.CLAUDE_NPM_LOCAL_PATH", fake_missing), \
patch("shutil.which", return_value="/usr/local/bin/claude"):
assert check_tool("claude") is True

def test_not_found_when_nowhere(self):
"""Should return False when claude is genuinely not installed."""
fake_missing = Path("/nonexistent/claude")

with patch("specify_cli.CLAUDE_LOCAL_PATH", fake_missing), \
patch("specify_cli.CLAUDE_NPM_LOCAL_PATH", fake_missing), \
patch("shutil.which", return_value=None):
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

These tests use a hard-coded absolute path (/nonexistent/claude) to represent a missing file. Prefer constructing a non-existent path under tmp_path (without creating it) so the tests are fully hermetic and don't depend on assumptions about the host filesystem.

Copilot uses AI. Check for mistakes.
Comment on lines +617 to +618
if (CLAUDE_LOCAL_PATH.exists() and CLAUDE_LOCAL_PATH.is_file()) or \
(CLAUDE_NPM_LOCAL_PATH.exists() and CLAUDE_NPM_LOCAL_PATH.is_file()):
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

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

The Claude local-path check does two filesystem stats per path (exists() then is_file()). Path.is_file() already returns False when the path doesn't exist, so the exists() calls are redundant. Consider simplifying to reduce repeated I/O and improve readability (and avoid the backslash continuation by using parentheses or any(...)).

Suggested change
if (CLAUDE_LOCAL_PATH.exists() and CLAUDE_LOCAL_PATH.is_file()) or \
(CLAUDE_NPM_LOCAL_PATH.exists() and CLAUDE_NPM_LOCAL_PATH.is_file()):
if CLAUDE_LOCAL_PATH.is_file() or CLAUDE_NPM_LOCAL_PATH.is_file():

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

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

Please address Copilot feedback. If not applicable, please explain why

@one-kash
Copy link
Author

You brought the big guns, I like it!

I might have to call my buddy Claude too ;)

- Remove unused pytest import from test_check_tool.py
- Use tmp_path instead of hardcoded /nonexistent/claude for hermetic tests
- Simplify redundant exists() + is_file() to just is_file()

AI-assisted: Changes applied with Claude Code.
@one-kash one-kash requested a review from mnriem March 26, 2026 23:19
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.

Claude Code CLI (not found)

3 participants