fix+docs(memory): atomic file write + expanded tool descriptions - #4049
Closed
yakuphanycl wants to merge 1 commit into
Closed
Conversation
External MCP audit (using https://github.com/yakuphanycl/wrg-skills/tree/main/skills/mcp-audit) of `src/memory/` surfaced one Low (state-handling) and one Info finding. Batched per the audit's disclosure SOP §2. ## Fix — non-atomic file write (Low, state-handling axis) `KnowledgeGraphManager.saveGraph()` truncate-and-wrote the live memory file directly via `fs.writeFile(memoryFilePath, ...)`. A crash mid-write (or a reader racing the writer) could observe an empty or torn file. There is no existing concurrency control, so the live file is the only canonical store. Changed to write-temp-then-rename: - Write all JSONL lines to `<memoryFilePath>.tmp.<pid>.<ts>` - `fs.rename` the temp into place — atomic on POSIX, same-volume best-effort on Windows. - On failure, best-effort `fs.unlink` the temp so a half-written file doesn't leak alongside the live one. This is a strict improvement: the live file is now always either the pre-existing valid snapshot or the new valid snapshot, never a torn one. It does NOT solve the loadGraph→mutate→saveGraph in-process race (two parallel tool calls can still each see the same pre-state and one's mutation can be lost). A follow-up to add an in-process write mutex is recommended; flagged in the audit's next-session backlog. Two new tests under `describe('atomic write')`: - Live file always parses as valid JSONL after a successful save. - No `<file>.tmp.*` stragglers left in the directory after a successful save. ## Docs — expand 9 Tool() descriptions (Info) The in-code `server.registerTool` descriptions were one-line WHAT statements ("Create multiple new entities in the knowledge graph"). The README has more context, but agents reading the MCP tool surface only see the in-code description. Rewrote all 9 to follow a consistent shape: - WHAT (preserved) - when-to-use guidance (one short clause; e.g., for `read_graph`: "for targeted lookups prefer search_nodes (by query) or open_nodes (by name) since payload size grows with the graph") - return-shape hint - silent-skip behaviour (the fact that duplicate creates, non-existent deletes, and unknown observation removals are silently skipped is now documented in the description, not just inferable from tests) No behavioural change to any tool dispatch path, no schema change, no new dependencies.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
External MCP audit of
src/memory/(using themcp-auditskill) surfaced one Low state-handling finding and one Info discoverability finding. Batched into a single PR per the audit's disclosure SOP §2.No behavioural change to any tool dispatch path, no schema change, no new dependencies.
Findings addressed
F-002 — Non-atomic file write (Low, state-handling axis)
KnowledgeGraphManager.saveGraph()truncate-and-wrote the live memory file directly viafs.writeFile(memoryFilePath, ...). Combined with the absence of any concurrency control, this means:loadGraphENOENT branch swallows the error and returns{entities: [], relations: []}), silently masking data loss.Fix: write to
<memoryFilePath>.tmp.<pid>.<ts>thenfs.renameinto place. Rename is atomic on POSIX and a same-volume best-effort on Windows. The live file is now always either the pre-existing valid snapshot or the new valid snapshot, never torn.Two new tests under
describe('atomic write'):<file>.tmp.*stragglers left after a successful save.Scope note: this fix does not address the broader
loadGraph→mutate→saveGraphin-process race (two parallel tool calls each read the same pre-state, one's mutation is overwritten). That requires an in-process write mutex and is flagged as a follow-up in the audit's next-session backlog. The atomic-write fix is a strict improvement that lands cleanly without that scope.F-003 — In-code Tool() descriptions lack when-to-use + return-shape (Info)
All 9
server.registerTooldescriptions were one-line WHAT-statements ("Create multiple new entities in the knowledge graph"). README has more context, but agents reading the MCP tool surface only see the in-code description.Rewrote all 9 to a consistent shape:
read_graph: "for targeted lookups prefersearch_nodes(by query) oropen_nodes(by name) since payload size grows with the graph")Findings deferred (not in this PR)
KnowledgeGraphManager, but none round-trip through theserver.registerToolhandler dispatch. Same gap as audit ofsrc/git. Worth a separate PR with an SDK-client integration fixture.async-mutex-style serializer or a chainedPromisepattern.Test plan
fs.writeFile+fs.rename— no new APIs, no type changes.EISDIR: illegal operation on a directory, symlink ... server-sequential-thinking) — same constraint documented in the auditor's filesystem case-study. Linux CI will run cleanly.Audit reference
yakuphanycl/wrg-skillswrg-skills/docs/case-studies/memory-mcp-2026-04-26.md🤖 Generated with Claude Code