You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On Windows, every Chroma sync fails with MCP error -32000: Connection closed. The chroma-mcp subprocess dies in ~18ms, long before it ever reaches the Chroma server. This happens regardless of whether Chroma (Docker/remote) is up and reachable.
The root cause is how the worker spawns chroma-mcp on Windows: it wraps the command in cmd.exe /c uvx .... The dependency specs onnxruntime>=1.20 and protobuf<7 contain > and <, which cmd.exe interprets as redirection operators.
The v13.4.0 fix (PR for "chroma-mcp cmd.exe quoting") added per-arg quoting for [<>|&^()], but this does not fix the problem and arguably makes it worse — because the MCP SDK spawns via an argv array with shell: false, so child_process applies its own Windows quoting on top of the manually-injected quotes, producing a malformed command line.
Environment
claude-mem: 13.4.0 (worker bundle confirmed running from the updated plugin dir)
The problem: StdioClientTransport spawns with shell: false and an argv array. On Windows, child_process already does its own command-line quoting for each arg. Manually wrapping args in " via Lse means the literal quote characters get re-escaped by child_process, corrupting the final command line cmd.exe receives.
Reproduction (isolated, no claude-mem)
Spawning cmd.exe /c uvx ... via child_process.spawn with shell: false, mirroring the SDK:
1. Raw args (pre-13.4.0 behavior):
spawn("cmd.exe",["/c","uvx","--python","3.13","--with","onnxruntime>=1.20","--with","protobuf<7","chroma-mcp==0.2.6","--client-type","http","--host","127.0.0.1","--port","8000","--ssl","false"],{shell:false})// → EXIT code=1, stderr: "The system cannot find the file specified."// (cmd.exe treats `>` as redirect to a file named `=1.20`)
2. Lse-quoted args (v13.4.0 behavior):
spawn("cmd.exe",["/c","uvx","--python","3.13","--with",'"onnxruntime>=1.20"',"--with",'"protobuf<7"', ...],{shell:false})// → EXIT code=1, stderr: "The directory name is invalid."// (child_process re-quotes the already-quoted args → malformed command line)
3. Spawn uvx.exe DIRECTLY, no cmd.exe (proposed fix):
spawn("C:/Users/<user>/.local/bin/uvx.exe",["--python","3.13","--with","onnxruntime>=1.20","--with","protobuf<7","chroma-mcp==0.2.6","--client-type","http","--host","127.0.0.1","--port","8000","--ssl","false"],{shell:false})// → process stays alive, "Installed 106 packages in 2.88s", clean stdout (chroma-mcp logs to stderr).// MCP stream is uncorrupted. Works.
Proposed fix
Do not route through cmd.exe on Windows. Spawn the uvx executable directly so shell metacharacters (>, <) are passed as literal argv values and never parsed by a shell:
// Resolve uvx.exe (PATHEXT-aware) once, then spawn directly on all platforms.constcommand=uvxPath;// e.g. resolved `uvx.exe`, or `uvx` on POSIXconstargs=e;// raw args, NO manual quoting, NO `["/c","uvx",...]`this.transport=newStdioClientTransport({ command, args, env,cwd: homedir(),stderr: "pipe"});
Notes:
Drop the Lse quoting entirely — it only exists to compensate for the cmd.exe wrapper.
If the cmd.exe wrapper was added because uvx can be a .cmd/.bat shim (which child_process can't spawn directly with shell:false), resolve the real executable via PATHEXT (prefer uvx.exe), or set shell: trueonly as a fallback when the resolved target is a .cmd/.bat. Spawning the .exe directly avoids the shell entirely and is the robust path.
Impact
Vector search is completely non-functional on Windows. Every observation/summary/prompt sync logs an error and falls back to no-vector-search.
Affects all Windows users on both local (persistent) and remote (http) Chroma modes — the failing layer is the subprocess spawn, before any client-type-specific logic runs.
Workaround until fixed
Set CLAUDE_MEM_CHROMA_ENABLED=false. Memory capture and injection continue to work via SQLite FTS; only semantic vector search is unavailable. Re-enabling later triggers a backfill, so no data is lost.
Bug: chroma-mcp subprocess dies instantly on Windows — the v13.4.0
cmd.exequoting fix (#2701) is incompleteRelated issues
cmd.exeinterpretsuvxversion constraints as redirection #2535, Windows: cmd.exe /c uvx wrapper breaks Chroma MCP stdio on WinGet uv installs (regression after #1190 / #1199) --> FIX attacched #2426, [Windows] chroma-mcp subprocess dies in 19-23ms — cmd /c wrapper breaks MCP stdio pipes #2425, Windows cmd.exe stdin redirect breaks chroma-mcp spawn — protobuf<7 arg interpreted as < redirect #2429, chroma-mcp fails to connect on Windows: cmd.exe misinterprets protobuf<7 as I/O redirection #2438, chroma-mcp connection fails immediately when spawned from bun daemon worker on Windows (works manually) #2484, chroma-mcp never starts on Windows:>=/<in hardcoded deps eaten by cmd.exe redirection (v13.2.0) #2495, Windows: chroma-mcp connection fails instantly — cmd.exe interprets < > in version constraints as redirection #2509, [Windows] chroma-mcp fails to start — '>' and '<' in version constraints interpreted as cmd.exe redirects #2529, chroma-mcp fails on Windows: cmd.exe interprets<and>in uvx args as I/O redirects #2585, Windows: Chroma/uvx spawn fails instantly — cmd.exe parses < / > in --with version pins as redirection #2591, chroma-mcp connection always fails on Windows due to unescaped>and<in spawn args #2405, Windows: Chroma semantic search broken — cmd /c wrapper mangles uvx --with version specifiers #2494.mcp-searchfails on Windows with-32000because it's launched viash(not on PATH). Same anti-pattern (assuming a shell on Windows), different component. The robust fix below applies to both: spawn the real executable directly withshell: false.Summary
On Windows, every Chroma sync fails with
MCP error -32000: Connection closed. Thechroma-mcpsubprocess dies in ~18ms, long before it ever reaches the Chroma server. This happens regardless of whether Chroma (Docker/remote) is up and reachable.The root cause is how the worker spawns
chroma-mcpon Windows: it wraps the command incmd.exe /c uvx .... The dependency specsonnxruntime>=1.20andprotobuf<7contain>and<, whichcmd.exeinterprets as redirection operators.The v13.4.0 fix (PR for "chroma-mcp cmd.exe quoting") added per-arg quoting for
[<>|&^()], but this does not fix the problem and arguably makes it worse — because the MCP SDK spawns via an argv array withshell: false, sochild_processapplies its own Windows quoting on top of the manually-injected quotes, producing a malformed command line.Environment
127.0.0.1:8000,/api/v2/version→1.0.0(verified reachable)CLAUDE_MEM_CHROMA_ENABLED=true,CLAUDE_MEM_CHROMA_MODE=remote, host127.0.0.1, port8000, sslfalseObserved log (Docker confirmed UP at this timestamp)
Subprocess lifetime:
.122→.141= 19ms. It never contacts port 8000.Root cause
Current Windows spawn logic in the worker (minified
worker-service.cjs,connectInternal):with the v13.4.0 quoting helper:
The problem:
StdioClientTransportspawns withshell: falseand an argv array. On Windows,child_processalready does its own command-line quoting for each arg. Manually wrapping args in"viaLsemeans the literal quote characters get re-escaped bychild_process, corrupting the final command linecmd.exereceives.Reproduction (isolated, no claude-mem)
Spawning
cmd.exe /c uvx ...viachild_process.spawnwithshell: false, mirroring the SDK:1. Raw args (pre-13.4.0 behavior):
2. Lse-quoted args (v13.4.0 behavior):
3. Spawn
uvx.exeDIRECTLY, no cmd.exe (proposed fix):Proposed fix
Do not route through
cmd.exeon Windows. Spawn theuvxexecutable directly so shell metacharacters (>,<) are passed as literal argv values and never parsed by a shell:Notes:
Lsequoting entirely — it only exists to compensate for thecmd.exewrapper.cmd.exewrapper was added becauseuvxcan be a.cmd/.batshim (whichchild_processcan't spawn directly withshell:false), resolve the real executable viaPATHEXT(preferuvx.exe), or setshell: trueonly as a fallback when the resolved target is a.cmd/.bat. Spawning the.exedirectly avoids the shell entirely and is the robust path.Impact
local(persistent) andremote(http) Chroma modes — the failing layer is the subprocess spawn, before any client-type-specific logic runs.Workaround until fixed
Set
CLAUDE_MEM_CHROMA_ENABLED=false. Memory capture and injection continue to work via SQLite FTS; only semantic vector search is unavailable. Re-enabling later triggers a backfill, so no data is lost.