fix(mem0-plugin): store files_touched as a list to stop double JSON-encoding (#5806)

This commit is contained in:
Kartik
2026-06-25 09:06:11 +05:30
committed by GitHub
parent b33fa5427c
commit ac8f862ff7
14 changed files with 129 additions and 21 deletions
@@ -1,6 +1,6 @@
{
"name": "mem0",
"version": "0.2.10",
"version": "0.2.11",
"description": "Persistent memory for Claude Code. Remembers decisions, patterns, and preferences across sessions.",
"author": {
"name": "Mem0",
@@ -1,6 +1,6 @@
{
"name": "mem0",
"version": "0.2.10",
"version": "0.2.11",
"description": "Persistent memory for Codex. Remembers decisions, patterns, and preferences across sessions.",
"author": {
"name": "Mem0",
@@ -1,6 +1,6 @@
{
"name": "mem0",
"version": "0.2.10",
"version": "0.2.11",
"description": "Mem0 memory layer for AI applications. Add persistent memory, personalization, and semantic search using the Mem0 Platform MCP server.",
"author": {
"name": "Mem0",
+12
View File
@@ -2,6 +2,18 @@
All notable changes to the Mem0 plugin will be documented in this file.
## 0.2.11 — Session-summary metadata fix + rerank auto-injected context by default
> Versions: Claude Code / Cursor / Codex `0.2.11`; Antigravity `0.1.3`. All four editors share `scripts/`, so the fix below applies to every editor.
### Fixed
- **`files_touched` was double-JSON-encoded in session summaries (`scripts/capture_session_summary.py`):** the Stop-hook summary set `metadata["files_touched"] = json.dumps(files[:20])` — a pre-serialized JSON string — and then serialized the whole request body again with `json.dumps(body)`. The stored memory therefore carried an escaped string blob (`"[\"mem0/memory/main.py\", \"src/client/index.ts\"]"`) instead of a real array, so file paths surfaced as backslash- and slash-heavy escaped text when those memories were returned by `search_memories`/`get_memories` and shown in Claude Code, Cursor, Codex, and Antigravity. The fix stores the list directly (`metadata["files_touched"] = files[:20]`) so the body is encoded exactly once. New `tests/test_capture_session_summary.py` asserts the posted body contains a JSON array and no escaped-string artifact.
### Changed
- **Auto-injected memory context is now reranked by default (`scripts/_search.py`, `scripts/file_context.py`, `scripts/on_bash_output.sh`, `scripts/on_user_prompt.sh`):** the REST search endpoint does not rerank when `rerank` is omitted, so hook-injected context (file-context, bash-error lookup, session-resume prefetch) was ordered by raw vector similarity and the single most relevant memory could fall outside the injected `top_k` window. A new `should_rerank()` helper turns reranking on for every auto-injection path; the extra ~150–200 ms stays within the hook's curl budget. Opt out with `MEM0_RERANK=0` (also accepts `false`/`no`/`off`). (#5690)
## 0.2.10 — Accurate per-editor telemetry attribution
### Fixed
+1 -1
View File
@@ -1,7 +1,7 @@
{
"id": "mem0",
"name": "mem0",
"version": "0.1.2",
"version": "0.1.3",
"description": "Persistent semantic memory for Antigravity agents. Cross-session, user-level recall via the Mem0 Platform MCP server. 16 slash commands, lifecycle hooks for auto-capture and metadata enforcement.",
"author": { "name": "Mem0", "email": "support@mem0.ai" },
"publisher": "mem0ai",
@@ -173,7 +173,7 @@ def store_summary(
if branch:
metadata["branch"] = branch
if files:
metadata["files_touched"] = json.dumps(files[:20])
metadata["files_touched"] = files[:20]
body = {
"messages": [{"role": "user", "content": summary_prompt}],
@@ -0,0 +1,79 @@
"""Regression tests for capture_session_summary.py request body construction.
Guards against the double-JSON-encoding bug where ``files_touched`` was stored
as a pre-serialized JSON string and then encoded a second time with the rest of
the request body — surfacing as escaped, slash-heavy blobs in the memories shown
inside Claude Code / Cursor / Codex / Antigravity (all four editors share this
script).
"""
from __future__ import annotations
import json
class _FakeResp:
status = 200
def __enter__(self):
return self
def __exit__(self, *_):
return False
def _capture_request_body(monkeypatch):
"""Patch urlopen so store_summary posts nowhere; capture the request body."""
import capture_session_summary as css
captured: dict = {}
def fake_urlopen(req, timeout=0):
captured["raw"] = req.data.decode("utf-8")
captured["body"] = json.loads(captured["raw"])
return _FakeResp()
monkeypatch.setattr(css.urllib.request, "urlopen", fake_urlopen)
return captured, css
def test_files_touched_is_json_array_not_double_encoded(monkeypatch):
"""files_touched must be a real JSON array, encoded exactly once."""
captured, css = _capture_request_body(monkeypatch)
files = ["mem0/memory/main.py", "src/client/index.ts"]
css.store_summary(
api_key="test-key",
summary_prompt="did some work",
user_id="u1",
session_id="s1",
project_id="p1",
branch="main",
files=files,
)
files_touched = captured["body"]["metadata"]["files_touched"]
assert isinstance(files_touched, list), (
"files_touched must be a JSON array, not a double-encoded string; "
f"got {type(files_touched).__name__}: {files_touched!r}"
)
assert files_touched == files
# The file paths must not appear as an escaped JSON string inside the body.
assert '\\"' not in captured["raw"]
def test_files_touched_omitted_when_no_files(monkeypatch):
"""No files touched -> no files_touched key (unchanged behaviour)."""
captured, css = _capture_request_body(monkeypatch)
css.store_summary(
api_key="test-key",
summary_prompt="did some work",
user_id="u1",
session_id="s1",
project_id="p1",
branch="main",
files=[],
)
assert "files_touched" not in captured["body"]["metadata"]