{"record":{"id":"9f912fe59d789220","repo":"headroomlabs-ai/headroom","slug":"cannot-update-memories-belonging-to-other-users","errorCode":null,"errorMessage":"Cannot update memories belonging to other users","messagePattern":"Cannot update memories belonging to other users","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"headroom/memory/backends/mem0_system_adapter.py","lineNumber":263,"sourceCode":"            ValueError: If memory not found.\n        \"\"\"\n        # Get the existing memory using Mem0 client directly\n        client = await self._backend._ensure_client()\n\n        try:\n            existing_data = await asyncio.to_thread(client.get, memory_id=memory_id)\n        except Exception:\n            existing_data = None\n\n        if not existing_data:\n            raise ValueError(f\"Memory not found: {memory_id}\")\n\n        # Extract user_id from existing data\n        existing_user_id = existing_data.get(\"user_id\", \"\")\n\n        # Validate user if provided\n        if user_id and existing_user_id and existing_user_id != user_id:\n            raise ValueError(\"Cannot update memories belonging to other users\")\n\n        # Build update metadata\n        now = _utcnow()\n        update_metadata: dict[str, Any] = {}\n        if reason:\n            update_metadata[\"update_reason\"] = reason\n            update_metadata[\"updated_at\"] = now.isoformat()\n\n        # Update via Mem0\n        try:\n            await asyncio.to_thread(\n                client.update,\n                memory_id=memory_id,\n                data=new_content,\n            )\n        except Exception as e:\n            raise ValueError(f\"Failed to update memory: {e}\") from e\n","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/memory/backends/mem0_system_adapter.py#L245-L281","documentation":"ValueError raised by Mem0SystemAdapter.update_memory() as an ownership guard: the caller passed a user_id, the stored memory carries a non-empty user_id, and the two differ. This prevents one tenant/user from mutating another user's memories through the update path.","triggerScenarios":"update_memory(memory_id, new_content, user_id='alice') where the memory's stored user_id is 'bob' (or a differently-formatted id like 'user:alice' vs 'alice').","commonSituations":"Multi-tenant apps passing the requester's id instead of the memory owner's id; user id normalization mismatches (prefixed ids, case, UUID vs handle); cross-user admin tooling that legitimately needs to update others' memories.","solutions":["Pass the memory owner's user_id (fetch the memory first to get its stored user_id) or omit user_id to skip the check","Normalize user ids at save and update time so the same string format is used everywhere","If intentional admin cross-user edits are required, call the update without the user_id argument after your own authorization check","Audit where the memory was created — it may have been saved under the wrong user_id originally"],"exampleFix":"# before\nawait adapter.update_memory(mem_id, 'text', user_id='alice')  # stored user_id='bob'\n# ValueError: Cannot update memories belonging to other users\n\n# after\nexisting = await asyncio.to_thread(client.get, memory_id=mem_id)\nawait adapter.update_memory(mem_id, 'text', user_id=existing['user_id'])","handlingStrategy":"validation","validationCode":"client = await adapter._backend._ensure_client()\nexisting = await asyncio.to_thread(client.get, memory_id=memory_id)\nif user_id and existing.get('user_id') and existing['user_id'] != user_id:\n    raise PermissionError(f'{user_id} cannot edit memory owned by {existing[\"user_id\"]}')","typeGuard":null,"tryCatchPattern":"try:\n    await adapter.update_memory(memory_id, new_content, user_id=requester_id)\nexcept ValueError as e:\n    if 'belonging to other users' in str(e):\n        return HTTP403  # authorization failure, do not retry\n    raise","preventionTips":["Normalize user ids (same format, case) at every save and update site","Do your own authorization before calling update; the guard is a safety net, not the policy","In tests, cover cross-user update attempts explicitly"],"tags":["python","mem0","authorization","ownership","multi-tenant","validation"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}