Summary
On Windows, /learning-loop:doctor reports 4 hard failures even when the binary, shims, and Claude Code are all installed and working. All four are false positives in the health-check probes (POSIX assumptions), not real install problems. Confirmed on main (matches plugin v1.33.0). The runtime path is fine: ll-search semantic search and indexing work; only the checker is wrong.
Verified on this machine that bin/ll-search.exe version returns 1.33.0, vault-search.mjs search returns ranked results, the .cmd shims exist, and claude --version works in the shell. Doctor still showed binary-exists, binary-runs, shims-exist, and claude-version as FAIL.
Environment
- Windows 11, PowerShell + Git Bash
- Node v24.16.0
- learning-loop 1.33.0, installed via Claude Code plugin marketplace
claude installed as npm global: claude, claude.cmd, claude.ps1 present in %APPDATA%\npm
Bugs and proposed fixes
1. Binary path omits .exe (drives binary-exists + binary-runs)
The native binary on Windows is ll-search.exe, but two probes look for extensionless ll-search, so existsSync returns false and binary-runs falls back to exit 127. Note plugin/scripts/lib/binary.mjs already gets this right (BINARY_NAME = process.platform === 'win32' ? 'll-search.exe' : 'll-search'), which is why runtime search works while the check fails.
plugin/scripts/health-check.mjs:113
plugin/scripts/lib/health-checks/quick.mjs:158
// before
const binaryPath = c.pluginData ? join(c.pluginData, 'bin', 'll-search') : null;
// after
const binaryName = process.platform === 'win32' ? 'll-search.exe' : 'll-search';
const binaryPath = c.pluginData ? join(c.pluginData, 'bin', binaryName) : null;
2. CLI shim check ignores .cmd (drives shims-exist)
install-shims.mjs writes ll-watch.cmd and ll-search.cmd on Windows, but checkShimsExist looks for extensionless names in ~/.local/bin, so it always reports them missing right after a successful install.
plugin/scripts/lib/health-checks/quick.mjs:275-276
// before
for (const s of ['ll-watch', 'll-search']) {
const p = join(home, '.local/bin', s);
// after
const shimExt = process.platform === 'win32' ? '.cmd' : '';
for (const s of ['ll-watch', 'll-search']) {
const p = join(home, '.local/bin', s + shimExt);
3. POSIX exec-bit test false-negative (binary-exists, shims-exist)
stat.mode & 0o111 is meaningless on Windows; Node statSync reports no POSIX exec bit for .exe/.cmd, so even after the path fix the binary reads as "not executable". Guard the test off on win32.
plugin/scripts/lib/health-checks/quick.mjs:171 (binary) and :283 (shims)
if (process.platform !== 'win32' && !(stat.mode & 0o111)) { ... }
4. safeExec skips PATHEXT, so claude/node .cmd shims are "not found" (claude-version)
execFileSync(cmd, args) without a shell does not honor PATHEXT on Windows, so execFileSync('claude', ['--version']) cannot find claude.cmd and the check reports "claude not found" (then suggests the Linux install.sh, which is wrong for Windows).
plugin/scripts/health-check.mjs:28-39
return execFileSync(cmd, args, {
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'ignore'],
timeout: 3000,
shell: process.platform === 'win32', // honor PATHEXT for .cmd shims
...opts,
}).trim();
Result after applying all four
19 pass / 1 warn / 4 fail becomes 23 pass / 1 warn / 0 fail. The remaining warn is local-bin-on-path, which is legitimate (informational).
Possibly related observation: two PLUGIN_DATA roots on Windows
The session-start hook injects PLUGIN_DATA=...\data\learning-loop-inline, but resolve-paths.mjs / download-binary.mjs resolve ...\data\learning-loop-learning-loop-marketplace. Both bin/ dirs end up populated with the binary, so it is not breaking, but download-binary.mjs reporting "already installed" in one root while the checker probes the other is confusing. Flagging in case it points at a path-derivation inconsistency.
Happy to open a PR with these four fixes if useful.
Summary
On Windows,
/learning-loop:doctorreports 4 hard failures even when the binary, shims, and Claude Code are all installed and working. All four are false positives in the health-check probes (POSIX assumptions), not real install problems. Confirmed onmain(matches plugin v1.33.0). The runtime path is fine:ll-searchsemantic search and indexing work; only the checker is wrong.Verified on this machine that
bin/ll-search.exe versionreturns1.33.0,vault-search.mjs searchreturns ranked results, the.cmdshims exist, andclaude --versionworks in the shell. Doctor still showedbinary-exists,binary-runs,shims-exist, andclaude-versionas FAIL.Environment
claudeinstalled as npm global:claude,claude.cmd,claude.ps1present in%APPDATA%\npmBugs and proposed fixes
1. Binary path omits
.exe(drivesbinary-exists+binary-runs)The native binary on Windows is
ll-search.exe, but two probes look for extensionlessll-search, soexistsSyncreturns false andbinary-runsfalls back to exit 127. Noteplugin/scripts/lib/binary.mjsalready gets this right (BINARY_NAME = process.platform === 'win32' ? 'll-search.exe' : 'll-search'), which is why runtime search works while the check fails.plugin/scripts/health-check.mjs:113plugin/scripts/lib/health-checks/quick.mjs:1582. CLI shim check ignores
.cmd(drivesshims-exist)install-shims.mjswritesll-watch.cmdandll-search.cmdon Windows, butcheckShimsExistlooks for extensionless names in~/.local/bin, so it always reports them missing right after a successful install.plugin/scripts/lib/health-checks/quick.mjs:275-2763. POSIX exec-bit test false-negative (
binary-exists,shims-exist)stat.mode & 0o111is meaningless on Windows; NodestatSyncreports no POSIX exec bit for.exe/.cmd, so even after the path fix the binary reads as "not executable". Guard the test off on win32.plugin/scripts/lib/health-checks/quick.mjs:171(binary) and:283(shims)4.
safeExecskips PATHEXT, soclaude/node.cmdshims are "not found" (claude-version)execFileSync(cmd, args)without a shell does not honor PATHEXT on Windows, soexecFileSync('claude', ['--version'])cannot findclaude.cmdand the check reports "claude not found" (then suggests the Linuxinstall.sh, which is wrong for Windows).plugin/scripts/health-check.mjs:28-39Result after applying all four
19 pass / 1 warn / 4 failbecomes23 pass / 1 warn / 0 fail. The remaining warn islocal-bin-on-path, which is legitimate (informational).Possibly related observation: two PLUGIN_DATA roots on Windows
The session-start hook injects
PLUGIN_DATA=...\data\learning-loop-inline, butresolve-paths.mjs/download-binary.mjsresolve...\data\learning-loop-learning-loop-marketplace. Bothbin/dirs end up populated with the binary, so it is not breaking, butdownload-binary.mjsreporting "already installed" in one root while the checker probes the other is confusing. Flagging in case it points at a path-derivation inconsistency.Happy to open a PR with these four fixes if useful.