In the hosted mem0 clients, calling delete all with an empty user ID sent a request the server reads as every memory in the project. This pull request stops that call inside the client, in Python and TypeScript, before anything goes over the wire. It closes the same hole in delete users, and three quieter ones around the same arguments.
In the hosted mem0 clients, calling delete all with an empty user ID sent a request the server reads as every memory in the project. This pull request stops that call inside the client, in Python and TypeScript, before anything goes over the wire. It closes the same hole in delete users, and three quieter ones around the same arguments.
Slide text
The bugThe fixThe proof
An empty user ID no longer deletes every memory in the project
PythonNode JS
client.delete_all(user_id="")
# before: DELETE /v1/memories/?user_id=
# after: ValueError, and no request is sent
The platform API reads entity IDs in two different places. Add and delete all read the user, agent, app and run IDs at the top level of the request. Search and get all read them inside filters. Both clients let callers mix the two up, and a blank ID went straight through to the server.
Slide text
The bugThe fixThe proof
The API reads entity IDs in two different places
At the top level
add() delete_all()
user_id="alice"
POST /v3/memories/add/ and DELETE /v1/memories/
Inside filters
search() get_all()
filters={"user_id": "alice"}
Entity IDs are user_id, agent_id, app_id and run_id
Here is what the old Python client put on the wire. Two calls could wipe data. Delete all with an empty user ID, and delete users with one, which fell through a truthiness check and deleted every entity. Three more failed quietly. A filters dict on delete all went out as a string and deleted nothing, the typed add options dropped the user ID, and filters on add got a 400.
Slide text
The bugThe fixThe proof
What the old Python client sent for five wrong inputs
CallRequest sentResult
delete_all(user_id="")
DELETE /v1/memories/?user_id=
Deletes every memory
delete_users(user_id="")
GET /v1/entities/, then DELETE user/alice, DELETE agent/bot
Deletes every entity
delete_all(filters={"user_id": "alice"})
DELETE /v1/memories/?filters=%7B%27user_id%27%3A…
Deletes nothing
add("hi", AddMemoryOptions(user_id="alice"))
POST /v3/memories/add/ {"messages":[...]}
Drops user_id
add("hi", filters={"user_id": "alice"})
POST /v3/memories/add/ {…,"filters":{...}}
400 from the server
The Python fix is two module level helpers. The first raises a value error whenever filters is set. None counts as not provided, because the proxy always passes filters as None. The second raises on any entity ID that is empty after stripping whitespace. A missing ID passes, and so does the star wildcard.
Slide text
The bugThe fixThe proof
Two helpers raise before any request is sent
mem0/client/main.py
PythonNode JS
def _reject_filters(method: str, params: Dict[str, Any]) -> None:
if params.get("filters") is not None:
raise ValueError(
f"filters is not supported in {method}(). Pass entity IDs at the top level, ..."
)
def _reject_blank_entity_ids(params: Dict[str, Any]) -> None:
for name, value in params.items():
if name in ENTITY_PARAMS and isinstance(value, str) and not value.strip():
raise ValueError(
f"{name} cannot be empty or whitespace-only: an empty ID would match every ..."
)
Each check runs in the sync and the async client. Delete all gets both checks. Delete users gets the blank ID check. Add only rejects filters, because a blank ID on add can't delete anything, so it is left alone. And the star wildcard still works. It stays the explicit way to delete everything.
Slide text
The bugThe fixThe proof
Each check runs where a wrong input does damage
Sync and async
Rejects filters
Rejects a blank ID
add()
Checked
Not checkeda blank ID on add deletes nothing
delete_all()
Checked
Checked
delete_users()
Not checkedIDs are named arguments
Checked
delete_all(user_id="*")still deletes every user's memories, on purpose
On the TypeScript side, the same two helpers sit next to the existing top level check. They run in add, delete all and delete users, before any fetch. In review, the question was whether the two clients really agree on blank IDs. They do. Same four fields, plus the camelCase forms, the same trim rule, and the same error text pointing at the star.
Slide text
The bugThe fixThe proof
The TypeScript client runs the same checks, camelCase keys included
“Please confirm the TS side also has the blank/whitespace entity-ID guard with identical semantics.”
It does. Same four fields plus userId, agentId, appId and runId. Same trim rule, "*" passes, and the error text matches.
The typed options had the contract backwards. Their docstrings said IDs go inside filters, so add memory options with a user ID, the call the migration guide shows, dropped it. The filters field is gone from both models, and the four entity IDs replace it. One gap is left. The models still allow extra fields, so a filters argument there is ignored, and the server answers with the same 400 as before.
Slide text
The bugThe fixThe proof
AddMemoryOptions and DeleteAllMemoryOptions now take the four entity IDs
Extra fields are still allowed, so AddMemoryOptions(filters=...) is ignored without an error, and the server returns the same 400 as before.
A sweep of internal callers found two that passed filters to add. The proxy's chat completions forwarded filters to add from a daemon thread, where the new error would be raised and lost. Filters now go only to search, and a regression test pins that. The second was the custom instructions docs example, which now passes the agent ID directly.
Slide text
The bugThe fixThe proof
The proxy now sends filters to search() and keeps them out of add()
Also fixed: the Python example in docs/platform/features/custom-instructions.mdx now passes agent_id="support-agent" instead of filters.
The tests were written before the fix. In Python, 66 new guard tests, and 58 of them failed against the old client. After the fix, they pass together with the existing client tests, just over a hundred in all. In TypeScript, 47 new tests, 40 failing before. After the fix, the whole suite passes, about 1,600 tests.
Slide text
The bugThe fixThe proof
The new tests failed first, then passed
Python
58 failed
of 66 new guard tests, before the fix
→
108 passed
guard tests plus tests/test_client.py
TypeScript
40 failed
of 47 new guard tests, before the fix
→
1,606 passed
full mem0-ts suite, 42 skipped
Then a live run, against a throwaway project with a one-off user ID. Filters on add now raise. Add with the typed options stores the memory, get all finds exactly one, and delete all with the typed options leaves zero. The TypeScript run matched. The empty ID delete was kept off the live project on purpose, since it could wipe it.
Slide text
The bugThe fixThe proof
Valid calls still add and delete on a live project
live check, throwaway project
PythonNode JS
add(filters={"user_id": ...}) -> ValueError: filters is not supported in add()
add(AddMemoryOptions(user_id=..., infer=False)) -> 'status': 'SUCCEEDED'
memory visible for user via get_all(filters): True count: 1
delete_all(DeleteAllMemoryOptions(user_id=...)) -> 'Delete in progress.'
memories remaining for user after delete_all: 0
Not run liveThe empty-ID delete. It could wipe the project, so its "before" comes from an offline request capture.
For anyone calling the clients, the change is one line. Entity IDs go at the top level of add and delete all, never in filters. The server still treats an empty ID as every memory and needs its own fix, and the version bump lands in a separate pull request. Until then, the clients are the guard, and an empty user ID no longer deletes every memory in the project.
Slide text
The bugThe fixThe proof
Pass entity IDs at the top level, and use "*" to delete on purpose