# How to Troubleshoot "Claude CLI Not Found" Errors in Claudian: A Complete Guide

> Fix Claude CLI not found errors with Claudian. Learn how Claudian's five-step resolution chain solves path issues, legacy settings, and environment variables for a seamless experience.

- Repository: [YishenTu/claudian](https://github.com/YishenTu/claudian)
- Tags: how-to-guide
- Published: 2026-03-17

---

**Claudian resolves the Claude CLI through a five-step resolution chain that checks per-host paths, legacy settings, environment variables, and built-in fallback directories, returning "not found" only when all checks fail.**

When the Obsidian plugin Claudian cannot locate the Claude CLI executable, it typically stems from misconfigured host-specific paths or the executable residing outside the default search directories. Understanding the resolution hierarchy implemented in [`src/utils/claudeCli.ts`](https://github.com/YishenTu/claudian/blob/main/src/utils/claudeCli.ts) allows you to diagnose exactly where the lookup fails. This guide explains the detection mechanism used by the YishenTu/claudian repository and provides concrete steps to resolve path issues.

## How Claudian Resolves the Claude CLI Path

The `ClaudeCliResolver` class in [`src/utils/claudeCli.ts`](https://github.com/YishenTu/claudian/blob/main/src/utils/claudeCli.ts) implements a prioritized resolution chain. When you trigger a Claude command, the resolver checks locations in the following order until it finds a valid executable file.

### 1. Per-Host Path Configuration

The resolver first checks `claudeCliPathsByHost[hostname]` using the key returned by `getHostnameKey()` from [`src/utils/env.ts`](https://github.com/YishenTu/claudian/blob/main/src/utils/env.ts). This allows different devices to maintain separate CLI paths without conflicting settings.

According to the source code at lines 27-35 of [`src/utils/claudeCli.ts`](https://github.com/YishenTu/claudian/blob/main/src/utils/claudeCli.ts), the resolver looks up the current hostname in the `claudeCliPathsByHost` map stored in [`claudian-settings.json`](https://github.com/YishenTu/claudian/blob/main/claudian-settings.json). If a match exists and the file is accessible, resolution stops here.

### 2. Legacy Single-Path Field

If no per-host entry exists, the system falls back to the legacy `claudeCliPath` field for backward compatibility. As implemented in [`src/utils/claudeCli.ts`](https://github.com/YishenTu/claudian/blob/main/src/utils/claudeCli.ts) (lines 36-43), this field accepts a single string path but is ignored if the per-host map contains an entry for the current machine.

### 3. Environment Variable Override

When both settings fields are empty or invalid, the resolver checks for a custom `PATH` environment variable. The `resolveClaudeCliPath` function calls `findClaudeCLIPath(customEnv.PATH)` at lines 68-73 of [`src/utils/claudeCli.ts`](https://github.com/YishenTu/claudian/blob/main/src/utils/claudeCli.ts), allowing you to specify alternative binary directories without modifying system PATH.

### 4. Built-in Directory Search

The `findClaudeCLIPath` function in [`src/utils/path.ts`](https://github.com/YishenTu/claudian/blob/main/src/utils/path.ts) (lines 64-82 and 88-115) performs an exhaustive scan of well-known installation locations. This includes:
- Home directory binaries (`~/.claude/local/`, `~/.local/bin/`)
- npm global prefixes (`/usr/local/lib/node_modules/`, `%APPDATA%/npm/`)
- Windows Program Files directories
- NVM (Node Version Manager) binary paths

### 5. OS-Specific Executable Names

Finally, the resolver adapts to operating system conventions. On Windows, it prefers `claude.exe` then [`cli.js`](https://github.com/YishenTu/claudian/blob/main/cli.js); on Unix systems, it checks for the `claude` binary before falling back to [`cli.js`](https://github.com/YishenTu/claudian/blob/main/cli.js) for npm-based installations (lines 77-89 and 96-104 of [`src/utils/path.ts`](https://github.com/YishenTu/claudian/blob/main/src/utils/path.ts)).

## Common Reasons for Resolution Failures

| Symptom | Root Cause | Verification Method |
|---------|------------|---------------------|
| **Null result** despite installation | Executable located outside all searched directories | Inspect `process.env.PATH` in the DevTools console or run `node -e "console.log(process.env.PATH)"` |
| **Per-host entry ignored** | Hostname key mismatch between `getHostnameKey()` output and settings JSON | Log the return value of `getHostnameKey()` and compare against keys in `claudeCliPathsByHost` |
| **Legacy path persistent** | `claudeCliPath` populated while `claudeCliPathsByHost` is empty, pointing to a deleted file | Clear the `claudeCliPath` field in [`claudian-settings.json`](https://github.com/YishenTu/claudian/blob/main/claudian-settings.json) |
| **Unexpanded variables** | Path contains `~` or `%VAR%` that `expandHomePath` failed to resolve | Test expansion manually: `node -e "console.log(require('./src/utils/path').expandHomePath('~/.local/bin/claude'))"` |
| **Broken symlinks** | `fs.statSync` detects the symlink but the target executable is missing | Run `ls -l <path>` on Unix or `Get-Item` on PowerShell to verify symlink integrity |

## Step-by-Step Troubleshooting Guide

Follow this diagnostic sequence to identify why the **"Claude CLI not found"** error appears:

1. **Inspect your settings file** located at [`.claudian-settings.json`](https://github.com/YishenTu/claudian/blob/main/.claudian-settings.json) in your vault root. Verify the structure contains either a valid `claudeCliPathsByHost` entry or an accurate `claudeCliPath`:

   ```json
   {
     "claudeCliPath": "",
     "claudeCliPathsByHost": {
       "my-hostname": "/usr/local/bin/claude"
     }
   }
   ```

2. **Validate the hostname key** by checking the output of `getHostnameKey()` from [`src/utils/env.ts`](https://github.com/YishenTu/claudian/blob/main/src/utils/env.ts). If your machine name changed or the key differs from your settings entry, update the JSON key to match.

3. **Test auto-detection manually** by opening the Obsidian Developer Console (`Ctrl+Shift+I`) and executing:

   ```javascript
   const { findClaudeCLIPath } = require('./src/utils/path.js');
   console.log(findClaudeCLIPath(process.env.PATH));
   ```

   If this returns `null`, the CLI is not in any standard location.

4. **Check fallback directories** manually to confirm installation location:

   ```bash
   # macOS/Linux

   ls -l ~/.claude/local/claude /usr/local/bin/claude ~/.local/bin/claude
   
   # Windows PowerShell

   Get-Item "$HOME\.claude\local\claude.exe", "$env:ProgramFiles\Claude\claude.exe"
   ```

5. **Verify npm installations** by checking for [`cli.js`](https://github.com/YishenTu/claudian/blob/main/cli.js) under global npm prefixes returned by `getNpmCliJsPaths()` in [`src/utils/path.ts`](https://github.com/YishenTu/claudian/blob/main/src/utils/path.ts) (lines 47-62).

6. **Restart Obsidian** after correcting paths to ensure `ClaudeCliResolver` clears its internal cache (the `reset()` method is called on plugin unload as seen in [`src/main.ts`](https://github.com/YishenTu/claudian/blob/main/src/main.ts) lines 258-276).

## Programmatic Debugging Examples

Use these code snippets to debug resolution logic directly within the Claudian plugin environment.

### Using the Resolver Programmatically

```typescript
import { ClaudeCliResolver } from '@/utils/claudeCli';
import type { HostnameCliPaths } from '@/core/types/settings';
import { getHostnameKey } from '@/utils/env';

const resolver = new ClaudeCliResolver();

function debugCliResolution(settings: any): string | null {
  const hostKey = getHostnameKey();
  const hostPaths: HostnameCliPaths = settings.claudeCliPathsByHost;
  const legacy = settings.claudeCliPath;
  const envText = process.env.CUSTOM_PATH || ''; // Empty uses real process.env
  
  console.log(`Resolving for host: ${hostKey}`);
  console.log(`Host paths map:`, hostPaths);
  console.log(`Legacy path:`, legacy);
  
  return resolver.resolve(hostPaths, legacy, envText);
}

```

### Manual Auto-Detection Test

```typescript
import { findClaudeCLIPath } from '@/utils/path';

// Test with current PATH
const detectedPath = findClaudeCLIPath(process.env.PATH);
console.log(`Detected CLI: ${detectedPath ?? 'Not found'}`);

// Test with custom PATH
const customPath = '/opt/claude/bin:/usr/local/bin';
console.log(findClaudeCLIPath(customPath));

```

### Fixing Stale Hostname Entries

```typescript
// Get current hostname key
const currentHost = require('./src/utils/env.js').getHostnameKey();

// Migrate settings
if (plugin.settings.claudeCliPathsByHost['old-laptop-name']) {
  plugin.settings.claudeCliPathsByHost[currentHost] = 
    plugin.settings.claudeCliPathsByHost['old-laptop-name'];
  delete plugin.settings.claudeCliPathsByHost['old-laptop-name'];
  await plugin.saveSettings();
}

```

## Key Source Files for Reference

Understanding these files helps you trace resolution logic:

- **[`src/utils/claudeCli.ts`](https://github.com/YishenTu/claudian/blob/main/src/utils/claudeCli.ts)** – Contains `ClaudeCliResolver` and `resolveClaudeCliPath`, the primary entry points for CLI resolution (lines 13-53).
- **[`src/utils/path.ts`](https://github.com/YishenTu/claudian/blob/main/src/utils/path.ts)** – Implements `findClaudeCLIPath` with exhaustive default locations and OS-specific handling (lines 64-115).
- **[`src/utils/env.ts`](https://github.com/YishenTu/claudian/blob/main/src/utils/env.ts)** – Provides `getHostnameKey()` for per-host lookups (lines 440-449).
- **[`src/core/types/settings.ts`](https://github.com/YishenTu/claudian/blob/main/src/core/types/settings.ts)** – Declares `claudeCliPath` (legacy) and `claudeCliPathsByHost` (preferred) TypeScript interfaces (lines 282-283).
- **[`src/main.ts`](https://github.com/YishenTu/claudian/blob/main/src/main.ts)** – Initializes the resolver on plugin load and handles cache reset on shutdown (lines 258-276).

## Summary

- **Claudian checks five locations** in strict order: per-host map, legacy path, custom environment, built-in directories, and OS-specific fallbacks.
- **Hostname mismatches** are the most common cause of per-host configuration failures; verify with `getHostnameKey()`.
- **Auto-detection scans** home directories, npm globals, and standard binary paths, but misses custom installation directories outside these locations.
- **Always restart Obsidian** after modifying CLI paths to clear the resolver cache.
- **Debug programmatically** using `findClaudeCLIPath()` and `ClaudeCliResolver` to see exactly which checks fail.

## Frequently Asked Questions

### Why does Claudian say "Claude CLI not found" when I can run `claude` in my terminal?

The plugin uses a specific resolution chain that may not include your CLI's directory if it is located in a non-standard path or added to shell-specific configuration files (like `.bashrc`) that Obsidian does not load. Run `findClaudeCLIPath(process.env.PATH)` in the DevTools console to see if the plugin detects your PATH correctly, or manually specify the full path in `claudeCliPathsByHost` using your hostname key.

### How do I find my hostname key for the per-host path setting?

The hostname key is generated by `getHostnameKey()` in [`src/utils/env.ts`](https://github.com/YishenTu/claudian/blob/main/src/utils/env.ts). To view your current key, open the Obsidian Developer Console and execute `require('./src/utils/env.js').getHostnameKey()`. Use this exact string as the key in your `claudeCliPathsByHost` JSON object.

### Can I use environment variables like `$HOME` or `%USERPROFILE%` in the path settings?

The resolution system attempts to expand paths using `expandHomePath` and `parsePathEntries` from [`src/utils/path.ts`](https://github.com/YishenTu/claudian/blob/main/src/utils/path.ts). While tilde (`~`) expansion is supported, complex environment variable syntax may fail. Use absolute paths when possible, or test expansion manually by importing the path utilities and verifying the output before saving settings.

### What is the difference between `claudeCliPath` and `claudeCliPathsByHost`?

`claudeCliPath` is a legacy single-string setting maintained for backward compatibility, while `claudeCliPathsByHost` is a map object allowing different paths for different machines. According to [`src/core/types/settings.ts`](https://github.com/YishenTu/claudian/blob/main/src/core/types/settings.ts), the per-host map takes precedence; if an entry exists for the current hostname, the legacy field is ignored entirely.