Skip to content

memory: characterization test — a schema-invalid record cannot be removed by any tool #5541

Open

Description

@reservoir-cmyk

Which server?

memory (@modelcontextprotocol/server-memory)

Protocol era

Modern (2026-07-28)

MCP client

No response

The problem

This is a characterization proposal, not a bug report. The unreadable-line preservation cases from #4885 pin that a schema-invalid record is skipped on read and written back verbatim on save — deliberately, per the comment at src/memory/index.ts:440-444, so data is never silently dropped. What is not pinned is the consequence: because the record does not exist in the graph, deleting it by name is a no-op, so the record persists forever and no tool surface can remediate it — only a text editor can. Pinning that consequence makes the state of affairs explicit, and if a remediation path is ever added, this test is the one that visibly changes.

Case

Write a file with one valid entity line and one schema-invalid entity line (missing entityType). Confirm the invalid record is invisible in readGraph. Call deleteEntities(["Broken"]) — it completes without error. Trigger an unrelated save. Assert the invalid line is still in the file verbatim, and the graph shows only the valid records. Mechanism: loadGraphWithUnreadable (index.ts:442-502) holds unreadable lines apart and saveGraph (:558) writes them back; deletion acts only on graph entities.

Solution you have in mind (optional)

Proposed test

For the ghost-line describe in src/memory/__tests__/knowledge-graph.test.ts:

it("cannot remove a schema-invalid record through any tool", async () => {
  const ghost = JSON.stringify({
    type: "entity",
    name: "Broken",
    observations: ["missing entityType"],
  });
  const lines = [
    JSON.stringify({
      type: "entity",
      name: "Alice",
      entityType: "person",
      observations: [],
    }),
    ghost,
  ];
  await fs.writeFile(testFilePath, lines.join("\n") + "\n");
  const manager = new KnowledgeGraphManager(testFilePath);

  const before = await manager.readGraph();
  expect(before.entities.map((e) => e.name)).toEqual(["Alice"]);

  await manager.deleteEntities(["Broken"]);
  await manager.createEntities([
    { name: "Carol", entityType: "person", observations: [] },
  ]);

  const saved = (await fs.readFile(testFilePath, "utf-8")).split("\n");
  expect(saved).toContain(ghost);
  const after = await manager.readGraph();
  expect(after.entities.map((e) => e.name)).toEqual(["Alice", "Carol"]);
});

Which MCP feature does it demonstrate? (optional)

No response

Alternatives or workarounds you have tried

Closest existing issue is #4885, which is the origin of the preservation behaviour itself — this proposal pins its consequence and is not a duplicate. No tool surface can remove such a record today; only a text editor can.

Already built it locally?

Verification

Ran against a byte-identical copy of v2/main @ 6cfff62: passed (with the expected "Skipping invalid entity" log on stderr). Full memory suite: 241 passed + 1 skipped. Framed as characterization of the current contract — it asserts today's behaviour, and takes no position on whether a remediation tool should exist; that call is the maintainers'.

Prompt used

"In src/memory's knowledge-graph tests, the unreadable-line preservation cases (#4885) pin that an invalid record is skipped on read and written back on save. Add the consequence case: write a file with one valid entity and one schema-invalid entity line, delete the invalid record BY NAME, trigger an unrelated save, and assert the invalid line is still in the file verbatim and the graph shows only valid records. Frame it as characterization of the current contract — no tool can remediate such a record today; only a text editor can."

Before you submit

  • I searched existing issues and this is not a duplicate.
  • This is a request for this repository or one of its servers, not a new server, the MCP specification or an SDK.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions