Summary
On Windows, semantic search silently degrades to FTS5 keyword search. Every Chroma sync and every search logs MCP error -32000: Connection closed, and the Chroma store is never populated.
Root cause
The worker spawns chroma-mcp through the command shell:
cmd.exe /c uvx --python 3.13 --with onnxruntime>=1.20 --with protobuf<7 chroma-mcp==0.2.6 --client-type persistent --data-dir ...\chroma
The dependency specifiers contain > and <, which cmd.exe interprets as redirection operators:
--with protobuf<7 makes cmd read stdin from a file named 7 → The system cannot find the file specified → child exits with code 1.
--with onnxruntime>=1.20 redirects stdout to a file.
The uvx command never runs, the child exits in ~200–500 ms, and the stdio transport reports Connection closed. The per-arg quoter (regex /[<>|&^()]/, wraps the arg in double quotes) does not survive node-spawn → cmd.exe re-parse, so it does not prevent the redirection.
Relevant code (worker-service.cjs, chroma manager connect):
let n = process.platform === "win32",
s = n ? process.env.ComSpec || "cmd.exe" : "uvx",
i = n ? ["/c", "uvx", ...e.map(quoteArg)] : e;
Proof
- Spawning the same
uvx with the identical args directly (no cmd.exe) succeeds: initialize returns, chroma_list_collections returns __NO_COLLECTIONS_FOUND__, and the server stays alive. The binary, the data dir, and the handshake are all healthy.
- The same args via
cmd.exe /c uvx ... exit code 1 with The system cannot find the file specified (the <7 input redirect from a nonexistent file 7).
Fix that works
Spawn the resolved uvx.exe path directly instead of through cmd.exe /c, so arguments pass verbatim through CreateProcess argv with no shell metacharacter parsing — the same path that already works on macOS/Linux. After this change on an affected machine: the deep probe reports healthy / connected / round-trip succeeded, chroma.sqlite3 grows from ~188 KB to ~97 MB, segment directories appear, and semantic search returns ranked results with no FTS5 fallback warning.
Caret-escaping the metacharacters for cmd.exe, or pinning the deps to metacharacter-free specifiers, would also work, but bypassing the shell is the cleanest and matches the non-Windows code path.
Environment
- Windows Server 2025 (10.0.26100), runner: bun
- claude-mem 13.4.x
- uv/uvx installed at
%USERPROFILE%\.local\bin
- chroma-mcp 0.2.6, chromadb server 1.6.0
Summary
On Windows, semantic search silently degrades to FTS5 keyword search. Every Chroma sync and every search logs
MCP error -32000: Connection closed, and the Chroma store is never populated.Root cause
The worker spawns chroma-mcp through the command shell:
The dependency specifiers contain
>and<, whichcmd.exeinterprets as redirection operators:--with protobuf<7makes cmd read stdin from a file named7→The system cannot find the file specified→ child exits with code 1.--with onnxruntime>=1.20redirects stdout to a file.The uvx command never runs, the child exits in ~200–500 ms, and the stdio transport reports
Connection closed. The per-arg quoter (regex/[<>|&^()]/, wraps the arg in double quotes) does not survive node-spawn → cmd.exe re-parse, so it does not prevent the redirection.Relevant code (worker-service.cjs, chroma manager connect):
Proof
uvxwith the identical args directly (nocmd.exe) succeeds:initializereturns,chroma_list_collectionsreturns__NO_COLLECTIONS_FOUND__, and the server stays alive. The binary, the data dir, and the handshake are all healthy.cmd.exe /c uvx ...exit code 1 withThe system cannot find the file specified(the<7input redirect from a nonexistent file7).Fix that works
Spawn the resolved
uvx.exepath directly instead of throughcmd.exe /c, so arguments pass verbatim throughCreateProcessargv with no shell metacharacter parsing — the same path that already works on macOS/Linux. After this change on an affected machine: the deep probe reportshealthy / connected / round-trip succeeded,chroma.sqlite3grows from ~188 KB to ~97 MB, segment directories appear, and semantic search returns ranked results with no FTS5 fallback warning.Caret-escaping the metacharacters for
cmd.exe, or pinning the deps to metacharacter-free specifiers, would also work, but bypassing the shell is the cleanest and matches the non-Windows code path.Environment
%USERPROFILE%\.local\bin