danielmiessler / danielmiessler/LifeOS

Windows: Inference.ts cannot spawn npm's claude.cmd (EINVAL) — memory reviewer never completes a run

Open
#2,057 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
19k
Forks
2.5k
Avg merge
8d 17h
Merged PRs (30d)
1

Description

## Summary

On Windows, `LIFEOS/TOOLS/Inference.ts` cannot spawn the Claude CLI at all. Every inference
call dies before reaching the model, so the memory reviewer has **never** completed a single
successful run on a fresh Windows install.

## Environment

- Windows 11, LifeOS 7.40.4
- `bun` 1.4.0, Node 22.9
- Claude Code CLI installed via `npm i -g @anthropic-ai/claude-code` (2.1.258)

## What happens

`resolveClaudeBin()` returns the path from `Bun.which("claude")`, which on Windows is npm's
shim **`claude.cmd`**. That path is handed to `spawn` imported from `node:child_process`,
which refuses to execute a `.cmd` without a shell and throws:

```
error: spawn C:\Users\\AppData\Roaming\npm\claude.cmd EINVAL
syscall: "spawn"
errno: -4071
code: "EINVAL"
```

Node tightened this in the CVE-2024-27980 fix: `.cmd` and `.bat` require `shell: true`.

## Observed downstream effect

`MemoryHealthCheck` reports `critical` with `reviewer-latest-timed-out` and
`priorSuccesses: 0`. The reviewer run directory contains `prompt.system.md`,
`prompt.user.md` and `transcript.txt` but no `response.raw.txt`, because inference returns
before writing one. The failure is silent from the user's perspective: the reviewer simply
never produces memory.

Worth noting `Bun.spawn` handles `.cmd` correctly; it is specifically the `node:child_process`
import that fails. So the bug is invisible to anyone testing the spawn path under Bun's own API.

## Suggested fix

Use `shell: true` on win32 when the resolved binary is a `.cmd`/`.bat`. cmd.exe does not
auto-quote, so arguments containing spaces **and empty-string arguments** must be quoted, or
the deliberate `--tools ''` and `--setting-sources ''` arguments silently vanish and change
the meaning of the invocation.

```ts
const claudeBin = resolveClaudeBin();
const needsShell = process.platform === 'win32' && /\.(cmd|bat)$/i.test(claudeBin);
const q = (a: string): string => (a === '' || /\s/.test(a) ? `"${a}"` : a);
const proc = spawn(
needsShell ? q(claudeBin) : claudeBin,
needsShell ? args.map(q) : args,
{ env, stdio: ['pipe', 'pipe', 'pipe'], ...(needsShell ? { shell: true, windowsHide: true } : {}) },
);
```

## Relationship to #2003

Fixing this uncovers #2003 as the *next* failure: once the spawn succeeds, cmd.exe's ~8191
character command-line limit rejects the reviewer's ~9 KB system prompt. The two are
sequential, not alternatives. Both must be fixed for the memory loop to work on Windows.

Verified locally: after applying the patch above plus the #2003 threshold change, a reviewer
run returns `ok: true`, `parse_ok: true`, with items dispatched and zero failures.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start in LIFEOS/TOOLS/Inference.ts at resolveClaudeBin() and the node:child_process spawn call, then reproduce the Windows claude.cmd failure. Verify that the Windows invocation preserves arguments containing spaces and empty strings, while non-Windows behavior remains unchanged. Done means a fresh Windows install can complete a reviewer run successfully, including response.raw.txt and parsed results.

Written by the indexing model from the issue text.

Assessment

Tech stack
bun, node.js, typescript
Domain
ai, cli
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.