Skip to content

fix(mem0): scope prefetch search and result cache by user_id - #43

Closed
dizhaky wants to merge 2 commits into
mainfrom
fix/mem0-prefetch-user-scoping
Closed

fix(mem0): scope prefetch search and result cache by user_id#43
dizhaky wants to merge 2 commits into
mainfrom
fix/mem0-prefetch-user-scoping

Conversation

@dizhaky

@dizhaky dizhaky commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • queue_prefetch() was ignoring its user_id argument and searching under self._user_id (set once at initialize() time). In shared gateway sessions where the cached AIAgent is reused across participants, this caused cross-user memory leakage.
  • prefetch() returned cached results without checking which user they were fetched for, so a result queued for User A could be injected into User B's next prompt.

Changes

  • queue_prefetch(): capture effective_user_id = user_id or self._user_id before spawning the background thread; pass {"user_id": effective_user_id} directly to client.search() instead of self._read_filters(); store result as (user_id, text) tuple.
  • prefetch(): discard the cached result when its owner user_id does not match the current caller — prevents stale cross-user results from leaking into the wrong session.
  • __init__: initialize _prefetch_result to None instead of "" to match the new tuple-or-None shape.

Test plan

  • In a shared gateway session (thread_sessions_per_user=False), send a message as User A, then immediately as User B — confirm User B does not receive User A's prefetched memories
  • Single-user CLI session still prefetches and injects context correctly
  • Verify existing unit tests in tests/plugins/memory/test_mem0_v2.py pass

Addresses Codex PR#33 P1 review comment: Scope prefetched memory by current user.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DvT8QMaMr4m9dQUAcVdypv


Generated by Claude Code


Note

Medium Risk
Fixes a privacy-sensitive cross-user leak in prompt injection; change is small and localized to prefetch only.

Overview
Fixes cross-user memory leakage when a shared gateway reuses one Mem0MemoryProvider across participants: background prefetch no longer ignores the per-turn user_id.

queue_prefetch now resolves effective_user_id from the call (fallback self._user_id), searches with filters={"user_id": effective_user_id} instead of instance-scoped _read_filters(), and stores the cache as (user_id, text).

prefetch only returns cached text when the tuple’s owner matches the current caller; otherwise it drops the cache and returns empty, so User A’s queued results cannot appear in User B’s prompt.

_prefetch_result is initialized to None to match the tuple-or-None cache shape.

Reviewed by Cursor Bugbot for commit 1c04ca5. Configure here.

queue_prefetch() was ignoring its user_id argument and always searching
under self._user_id (set at initialize() time). In shared gateway
sessions where the cached AIAgent is reused across participants, this
caused cross-user memory leakage: one user could receive prefetched
memories originally fetched for a different user.

Fixes:
- queue_prefetch(): capture effective_user_id before spawning thread,
  pass {"user_id": effective_user_id} directly instead of self._read_filters()
- Store prefetch result as (user_id, text) tuple so the consumer can
  verify ownership
- prefetch(): discard the cached result when its user_id does not match
  the current request — prevents stale cross-user results from leaking

Addresses Codex PR#33 P1 review comment.
@github-actions

Copy link
Copy Markdown

🔎 Lint report: fix/mem0-prefetch-user-scoping vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 8649 on HEAD, 8649 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 4570 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Empty prefetch still injects header
    • Added a check for an empty cached text element in prefetch so a (user_id, "") tuple no longer returns a header-only Mem0 section.

Create PR

Or push these changes by commenting:

@cursor push 3ede6df721
Preview (3ede6df721)
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
@@ -241,7 +241,7 @@
         with self._prefetch_lock:
             cached = self._prefetch_result
             self._prefetch_result = None
-        if not cached or cached[0] != effective_user_id:
+        if not cached or cached[0] != effective_user_id or not cached[1]:
             return ""
         return f"## Mem0 Memory\n{cached[1]}"

You 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 1c04ca5. Configure here.

if not cached or cached[0] != effective_user_id:
return ""
return f"## Mem0 Memory\n{result}"
return f"## Mem0 Memory\n{cached[1]}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty prefetch still injects header

Low Severity

When queue_prefetch gets Mem0 hits but every memory field is empty, the cache becomes a (user_id, "") tuple. prefetch treats any truthy tuple as valid and returns ## Mem0 Memory with no bullets. Previously an empty joined string made prefetch return nothing, so prefetch_all skipped injection.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1c04ca5. Configure here.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix applied in commit 3a3d3ce — added or not cached[1] to the guard in prefetch(). The empty-text tuple case now correctly returns "" rather than injecting a bare ## Mem0 Memory header with no bullets.

Reviewed and confirmed by Claude Code.


Generated by Claude Code

…njection

When queue_prefetch gets results but all memory fields are empty, cached
becomes (user_id, ""). prefetch was treating any truthy tuple as valid,
returning "## Mem0 Memory
" with no bullets. Add not cached[1] guard.

Fixes Cursor Bugbot finding on PR #43.

dizhaky commented Jun 28, 2026

Copy link
Copy Markdown
Owner Author

Applied the Cursor Bugbot fix for Empty prefetch still injects header (commit 3a3d3ce).

Analysis: The bug is valid. In queue_prefetch, when Mem0 returns results but all memory fields are empty/falsy, lines becomes an empty list and the cache is set to (user_id, ""). The prefetch method was checking if not cached or cached[0] != effective_user_id — but a (user_id, "") tuple is truthy, so it would return ## Mem0 Memory\n with no bullet points.

Fix applied:

-        if not cached or cached[0] != effective_user_id:
+        if not cached or cached[0] != effective_user_id or not cached[1]:
             return ""

This is low-risk, correct, and matches the Bugbot autofix exactly.


Generated by Claude Code

@dizhaky dizhaky closed this Jun 28, 2026
@dizhaky
dizhaky deleted the fix/mem0-prefetch-user-scoping branch June 28, 2026 08:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant