Skip to content

Windows: 4 false-positive health-check failures (.exe path, .cmd shims, POSIX exec-bit, PATHEXT) #3

Description

@zbolton36

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions