mcp-server.js still saves intelligence.json non-atomically and swallows corrupt loads — #634 / #698 fixed cli.js only
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 4.5k
- Forks
- 603
- Avg merge
- 23h 32m
- Merged PRs (30d)
- 59
Description
Describe the bug
#634 reported that Intelligence.save() overwrites .ruvector/intelligence.json with a plain fs.writeFileSync and that load() swallows a parse error into empty defaults, so a concurrent reader that lands mid-write silently restarts from an empty store. #698 fixed both in bin/cli.js (atomicWriteFileSync, readIntelStoreSafe quarantining to .corrupt-<epoch>), but bin/mcp-server.js has its own copy of the Intelligence class that was not touched:
mcp-server.jsIntelligence.save()still ends infs.writeFileSync(this.intelPath, JSON.stringify(this.data, null, 2))— line 396 in both 0.2.41 and 0.3.1 (line 320 in 0.2.34).mcp-server.jsIntelligence.load()still wraps the read intry { … } catch {}and returns{ patterns: {}, memories: [], … }on any parse failure, with no quarantine.
Under Claude Code the MCP server is the long-lived writer (every hooks_remember call goes through it) while each PreToolUse/PostToolUse/SessionStart hook is a separate short-lived ruvector hooks … CLI process reading the same file. The server's save therefore still opens the torn-file window #634 described; the fixed CLI side now detects the torn read and quarantines the file, but the store the user ends up with is whatever the hook wrote next.
What we observed (ruvector 0.2.34 server, 2026-09-17): a 9 MB onnx-minilm-stamped store with 757 memories was being saved by the MCP server while ~12 parallel subagents fired PostToolUse hooks. A hook-side CLI read the half-written file, moved it to intelligence.json.corrupt-1789669812640, and created a fresh store — hash-stamped, 64-dim, because the hook path resolved the hash embedder — so hooks reembed --dry-run then reported an embedder-provenance MISMATCH against a store that had been fine an hour earlier. The quarantined file was complete and parsed with all 757 memories; the server still had the good snapshot in memory. The same sequence is reachable on 0.3.1 because the server-side write is unchanged.
To Reproduce
- Start the MCP server (
ruvector mcp start, or via a Claude Code plugin) against a store of a few MB with vector memories. - In a loop, call
hooks_rememberthrough the server while running ~20 parallelnpx ruvector@0.3.1 hooks route "x"/hooks remember "y" -t testCLI processes against the same.ruvector/. - Eventually a CLI process reads mid-write: with 0.2.41+ it prints the quarantine error and
intelligence.json.corrupt-<epoch>appears; the server's nextsave()then overwritesintelligence.jsonfrom memory, or a CLI process creates a fresh store first, depending on timing.
Deterministic variant (no race): while the server is running, truncate intelligence.json mid-file, then trigger a server-side operation that calls load() (e.g. restart the server) — load() returns empty defaults with no error and the next save() persists the empty store.
Expected behavior
mcp-server.jsIntelligence.save()writes to a unique temp file in the same directory andrename()s over the target, ascli.jsdoes since #698 (ideally by sharing that helper rather than a second copy).mcp-server.jsIntelligence.load()fails loud (or quarantines, likereadIntelStoreSafe) on an unparseable store instead of returning empty defaults that the nextsave()persists.- Reader side:
rename()alone does not protect a reader that already opened the old inode before the rename (see npm/write-file-atomic#64), so a parse-retry once (re-open + re-read) before quarantining would avoid quarantining a file that was simply replaced under the reader.
Environment
- OS: Linux 6.18 (WSL2, Ubuntu 24.04)
- Node: 24.15.0 (also reproduced reading with 22.22.0)
- Ruvector version: 0.2.34 (observed); 0.2.41 and 0.3.1 verified to still have the plain
writeFileSyncatbin/mcp-server.js:396
Additional context
- #634, #698 (cli.js fix), #635 / #697 (same class in the rvf id-map sidecar).
- Downstream write-up with recovery steps: yellow-plugins
docs/solutions/integration-issues/ruvector-adr210-embedding-provenance-refusal.md, "Non-atomic store write race".
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by comparing the Intelligence.save() and Intelligence.load() implementations in bin/mcp-server.js with atomicWriteFileSync and readIntelStoreSafe in bin/cli.js. Reproduce the concurrent or truncated-file scenarios described, then verify that the MCP server uses atomic replacement, retries or quarantines unreadable stores, and does not silently persist empty defaults.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, nodejs
- Domain
- backend, cli
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100