fix(memory): pass user_id through prefetch + scope per-user — Codex PR#33 P1 - #39
fix(memory): pass user_id through prefetch + scope per-user — Codex PR#33 P1#39dizhaky wants to merge 17 commits into
Conversation
sync_turn accepted user_id but _write_filters() always read self._user_id set at provider initialize() time. In shared gateway sessions, User B's turn was synced under User A's Mem0 namespace.
queue_prefetch scoped the gateway request per-user but stored the result in a single _prefetch_result. In shared-thread sessions, User B could consume User A's prefetch context. Now track _prefetch_result_user and discard on mismatch.
Thread user_id from queue_prefetch_all (which already scoped requests) through to prefetch() so providers can validate result ownership.
…R#33 P1 Thread the per-turn user identity to prefetch_all so providers can validate which user's prefetch result to return.
…#33 P1 _init_cached_agent_for_turn only reset timing/API counters. In shared-thread sessions (thread_sessions_per_user=False), a reused AIAgent kept its prior _user_id/_user_name/_chat_id — so User B's turn synced memory under User A's identity. Refresh all caller-identity fields from source after init.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
🔎 Lint report:
|
|
Closing: all commits were unsigned (cursor-agent origin). Replacing with a single signed squash commit in #41. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Mem0 sync prefetch scope split
- Mem0 queue_prefetch now searches with the runtime effective_user_id and tags cached results so prefetch() discards buffers from a different user.
- ✅ Fixed: Memgw prefetch discard skips empty
- Memgw prefetch now discards cached results whenever _prefetch_result_user != user_id, including when the requesting user_id is an empty string.
Or push these changes by commenting:
@cursor push bcf492821a
Preview (bcf492821a)
diff --git a/plugins/memory/mem0/__init__.py b/plugins/memory/mem0/__init__.py
--- a/plugins/memory/mem0/__init__.py
+++ b/plugins/memory/mem0/__init__.py
@@ -128,6 +128,7 @@
self._agent_id = "hermes"
self._rerank = True
self._prefetch_result = ""
+ self._prefetch_result_user: str = ""
self._prefetch_lock = threading.Lock()
self._prefetch_thread = None
self._sync_thread = None
@@ -235,11 +236,16 @@
)
def prefetch(self, query: str, *, session_id: str = "", user_id: str = "") -> str:
+ effective_user_id = user_id or self._user_id
if self._prefetch_thread and self._prefetch_thread.is_alive():
self._prefetch_thread.join(timeout=3.0)
with self._prefetch_lock:
+ if self._prefetch_result_user != effective_user_id:
+ self._prefetch_result = ""
+ self._prefetch_result_user = ""
result = self._prefetch_result
self._prefetch_result = ""
+ self._prefetch_result_user = ""
if not result:
return ""
return f"## Mem0 Memory\n{result}"
@@ -248,12 +254,14 @@
if self._is_breaker_open():
return
+ effective_user_id = user_id or self._user_id
+
def _run():
try:
client = self._get_client()
results = self._unwrap_results(client.search(
query=query,
- filters=self._read_filters(),
+ filters={"user_id": effective_user_id},
rerank=self._rerank,
top_k=5,
))
@@ -261,6 +269,7 @@
lines = [r.get("memory", "") for r in results if r.get("memory")]
with self._prefetch_lock:
self._prefetch_result = "\n".join(f"- {l}" for l in lines)
+ self._prefetch_result_user = effective_user_id
self._record_success()
except Exception as e:
self._record_failure()
diff --git a/plugins/memory/memgw/__init__.py b/plugins/memory/memgw/__init__.py
--- a/plugins/memory/memgw/__init__.py
+++ b/plugins/memory/memgw/__init__.py
@@ -290,7 +290,7 @@
self._prefetch_thread.join(timeout=3.0)
with self._prefetch_lock:
# Discard a result queued for a different user to prevent cross-user leak.
- if user_id and self._prefetch_result_user and self._prefetch_result_user != user_id:
+ if self._prefetch_result_user != user_id:
self._prefetch_result = ''
self._prefetch_result_user = ''
result = self._prefetch_resultYou can send follow-ups to the cloud agent here.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 16e73b0. Configure here.
| {"role": "assistant", "content": assistant_content}, | ||
| ] | ||
| client.add(messages, **self._write_filters()) | ||
| client.add(messages, user_id=effective_user_id, agent_id=self._agent_id) |
There was a problem hiding this comment.
Mem0 sync prefetch scope split
High Severity
When a memgw agent is reused, queue_prefetch may perform memory recall using a stale self._user_id (from agent initialization) instead of the current user_id provided. This can lead to a user receiving prefetched memory content belonging to a different user, as the prefetch guard checks the intended user for the result, not the user actually used for the recall.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 16e73b0. Configure here.
There was a problem hiding this comment.
Addressed in subsequent merged PRs:
- PR fix(memory): pass user_id through prefetch + scope per-user (Codex PR#33 P1) #41 (fix(memory): pass user_id through prefetch + scope per-user) —
queue_prefetchnow receives and forwards the per-turnuser_id. - PR fix(mem0): scope prefetch search and result cache by user_id #44 (fix(mem0): scope prefetch search and result cache by user_id) — both Mem0 and memgw
queue_prefetchuse the kwarguser_idfor their search/recall, notself._user_id.
This PR (#39) was superseded by #41 and closed without merging.
Reviewed and confirmed by Claude Code.
Generated by Claude Code
| # Discard a result queued for a different user to prevent cross-user leak. | ||
| if user_id and self._prefetch_result_user and self._prefetch_result_user != user_id: | ||
| self._prefetch_result = '' | ||
| self._prefetch_result_user = '' |
There was a problem hiding this comment.
Memgw prefetch discard skips empty
Medium Severity
The new cross-user prefetch guard only clears cached results when both the current user_id and _prefetch_result_user are truthy. If the active turn has an empty user_id, a buffer tagged for another user can still be returned from prefetch().
Reviewed by Cursor Bugbot for commit 16e73b0. Configure here.
There was a problem hiding this comment.
Addressed in PR #44 (fix(mem0): scope prefetch search and result cache by user_id): the guard now also discards cached results when user_id is empty or falsy (or not cached[1]), preventing a stale result from leaking into an anonymous/empty-user turn.
Reviewed and confirmed by Claude Code.
Generated by Claude Code



Summary
user_idthroughprefetch_alland all providerprefetch()calls, enabling per-user memory scopingMemoryProviderbase class and all 9 plugins (mem0, honcho, hindsight, memgw, holographic, retaindb, supermemory, openviking, byterover) with theuser_idparametermemgwprefetch results byuser_idso cross-user memory contamination is impossibleSupersedes #36 (was behind main after #37 merged; rebased onto latest main).
Fixes DAN-xxx — Codex PR#33 P1 cross-user identity + prefetch scoping.
🤖 Generated with Claude Code
Note
Medium Risk
Changes identity and memory scoping on shared sessions and prefetch caches; incorrect user_id handling could leak or mis-attribute memories, though the memgw guard and gateway refresh directly target that risk.
Overview
Per-user memory prefetch now threads
user_idfrom the agent throughMemoryManager.prefetch_all()into every memory provider’sprefetch()hook, so recall can be scoped to the current caller instead of sharing one anonymous session.On gateway cached-agent reuse, caller identity (
_user_id, names, chat/thread ids) is refreshed each turn so shared-thread sessions (thread_sessions_per_user=False) don’t keep serving the previous user’s identity into memory or the loop.memgw tags background prefetch results with the requesting
user_idand drops cached prefetch when a different user reads it, blocking cross-user context leaks. mem0sync_turnwrites withuser_idfrom the turn (or the configured default) via explicituser_id/agent_idonclient.add.Reviewed by Cursor Bugbot for commit 16e73b0. Configure here.