Skip to content

Fix #34: memos add 写入成功但 list/get 查不到:user_id 作用域在写入侧被静默丢弃 - #36

Open
Memtensor-AI wants to merge 2 commits into
MemTensor:mainfrom
Memtensor-AI:bugfix/autodev-34-20260825080924845
Open

Fix #34: memos add 写入成功但 list/get 查不到:user_id 作用域在写入侧被静默丢弃#36
Memtensor-AI wants to merge 2 commits into
MemTensor:mainfrom
Memtensor-AI:bugfix/autodev-34-20260825080924845

Conversation

@Memtensor-AI

Copy link
Copy Markdown
Collaborator

Description

Fixed the user_id scope asymmetry reported in issue #34, where memos add would report success but the appended memory could not be read back by memos get / memos list / memos search.

Root cause: the write path silently dropped a resolved user_id. MemoryAPI.add_memory built its POST /add/message body by iterating common fields and only including values that were not None, so when config.defaults.user_id was None (config explicitly nulled it, or init-time default backfill was missing) and no --user-id flag was passed, the memory was stored with no user scope. Every read path, by contrast, resolves and requires a user_id (get_memories even raises APIError("Get memory requires user_id") when missing). An unscoped record is therefore never returned by a scoped lookup, and it persists after logout/login.

Changes:

  • memory_api.pyadd_memory now requires a non-empty user_id and always includes it in the request body; it raises APIError("Add memory requires user_id") instead of writing an unscoped memory. search_memories scope checks switched from truthiness to is not None for user_id/conversation_id/agent_id/app_id so empty-string scopes are handled consistently with add/get.
  • memory_cmd.pycmd_get takes a command_name parameter (default get) so the agent-mode envelope can label the alias correctly.
  • memory.py — added a list command that delegates to cmd_get with command_name="list".
  • main.py — registered list (with explicit name="list" to avoid Typer inferring None from the built-in-shadowing callback name) and added it to the help ordering.
  • MemOS CLI.md — noted that memos list is an alias of memos get.
  • tests/test_memory_scope.py — 8 new regression tests.

Test results: the full suite passes (49 tests OK, up from 41). Pre-existing tests that initially appeared failing were environment/import errors resolved by pip install -e .; after installation the whole suite is green. There is no configured linter in this repo, so validation used python -m py_compile on all changed modules (passed) plus the unittest suite. CLI smoke test confirmed memos list is now discoverable: memos --help lists list List memories for a user (alias for get) and memos list --help exits 0.

Outputs: opsp artifacts (task file + proposal/spec/design/verification-report) were written in the working repo and archived to the specs repo at 2026-08-25-34-memos-add-写入成功但-listget-查不到userid-作用域在写入侧被静默丢弃/.

Related Issue (Required): Fixes #34

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (does not change functionality, e.g. code style improvements, linting)
  • Documentation update

How Has This Been Tested?

Not run; documentation-only change.

  • Unit Test
  • Test Script Or Test Steps (please provide)
  • Pipeline Automated API Test (please provide)

Checklist

  • I have performed a self-review of my own code
  • I have commented my code in hard-to-understand areas
  • I have added tests that prove my fix is effective or that my feature works
  • I have created related documentation issue/PR in MemOS-Docs (if applicable)
  • I have linked the issue to this PR (if applicable)
  • I have mentioned the person who will review this PR

@lijicode please review this PR.

Reviewer Checklist

add_memory no longer drops a None user_id from the POST body; it requires
a non-empty user_id and always includes it, so writes never store an
unscoped memory that scoped reads (get/search) can never return. search
scoping now uses is not None to stay consistent with add/get. Register
memos list as an alias of get, matching MemOS CLI.md docs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Memtensor-AI Memtensor-AI added ai:generated Generated or modified by AI | 由 AI 生成或修改 area:core MOS 编排层 / 框架底座 / 跨模块问题 status:in-progress Someone or AI is working on it | 人工或 AI 正在处理 labels Aug 25, 2026
@Memtensor-AI
Memtensor-AI requested a review from lijicode August 25, 2026 09:25
@Memtensor-AI

Copy link
Copy Markdown
Collaborator Author

🤖 Open Code Review

Target: PR #36
Task: 3cd3efd8a4010a9b
Base: main
Head: bugfix/autodev-34-20260825080924845
Head SHA: 75718a4198ac93a0725f4fbc4f7de50f22caf33a

🔍 OpenCodeReview found 3 issue(s) in this PR.

⚠️ 1 warning(s) occurred during review.


1. src/memos_cli/backend/memory_api.py (L150-L151)

The add_memory validation uses a falsy check (if not user_id), which rejects "", but the chat method was updated to use is not None, which accepts "" and would forward an empty string as user_id to the API. This is inconsistent with add_memory and can silently reproduce the invisible-scope bug this change is meant to fix. The comment on this line claims these are the "same scoping rules as add_memory", which is incorrect.

Suggestion: either add an explicit empty-string guard in the chat path (if kwargs.get("user_id") not in (None, ""):), or align add_memory to use is None and add a separate explicit empty-string validation so both sides enforce the same contract.

💡 Suggested Change

Before:

        if kwargs.get("user_id") is not None:
            payload["user_id"] = kwargs["user_id"]

After:

        if kwargs.get("user_id") not in (None, ""):
            payload["user_id"] = kwargs["user_id"]

2. tests/test_memory_scope.py (L119-L120)

registered_commands is an undocumented internal attribute of typer.Typer, not part of Typer's public API. It may be absent or renamed in other Typer versions, causing this test to raise AttributeError instead of a meaningful assertion failure.

A more stable approach is to invoke the CLI through Typer's CliRunner (from typer.testing) and confirm the list command is reachable, or to check the registered command names via the Click group's commands dict — which is part of Click's stable public API:

from typer.testing import CliRunner
runner = CliRunner()
result = runner.invoke(main.app, ["list", "--help"])
self.assertEqual(result.exit_code, 0)

or:

click_group = typer.main.get_command(main.app)
self.assertIn("list", click_group.commands)
💡 Suggested Change

Before:

        command_names = {cmd.name for cmd in main.app.registered_commands}
        self.assertIn("list", command_names)

After:

        from typer.testing import CliRunner
        runner = CliRunner()
        result = runner.invoke(main.app, ["list", "--help"])
        self.assertEqual(result.exit_code, 0)

3. src/memos_cli/main.py (L17)

Importing list from memos_cli.commands.memory shadows Python's built-in list type. This breaks the type annotation on line 67 (-> list[str]), which now refers to the command function instead of the built-in collection type, causing a TypeError at runtime when the annotation is evaluated.

Rename the imported command to avoid the collision, for example:

from memos_cli.commands.memory import (
    add, extract, feedback, rerank, search, chat,
    get, list as list_cmd, delete, origin
)

Then register it with the explicit CLI name to preserve the user-facing command name:

app.command(name="list", rich_help_panel="Memory Operations")(list_cmd)

And update the HELP_COMMAND_ORDER list construction at line 67 to keep using the built-in list type.

💡 Suggested Change

Before:

from memos_cli.commands.memory import add, extract, feedback, rerank, search, chat, get, list, delete, origin

After:

from memos_cli.commands.memory import add, extract, feedback, rerank, search, chat, get, list as list_cmd, delete, origin

🧹 Filtered 1 low-confidence OCR finding(s) before posting/fix-loop (duplicate: 1).

Generated by cloud-assistant via Open Code Review.

@Memtensor-AI

Copy link
Copy Markdown
Collaborator Author

🔧 Open Code Review requested Agent fix

Open Code Review found 3 issue(s). I have resumed the development Agent to fix them.

  • Task: 3cd3efd8a4010a9b
  • Fix attempt: 1/2
  • Finding delta: 0 repeated / 3 new / 0 likely resolved

The Agent will push a new commit to this PR branch. OCR will recheck after the commit is pushed.

- search_memories: drop empty-string user_id (not just None) so the read
  contract matches add_memory/get_memories (both raise on falsy user_id).
  Sending user_id="" to /search/memory would filter by an empty scope on
  the server and never match memories written under a real user, silently
  reproducing the invisible-scope bug this PR was meant to fix.

- main.py: import commands.memory.list as list_cmd so it no longer shadows
  the builtin list type used by CommandFirstTyperGroup.list_commands'
  -> list[str] annotation.

- tests: replace app.registered_commands (undocumented Typer internal)
  with typer.main.get_command(app).commands, which uses Click's stable
  public API and won't break across Typer versions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Memtensor-AI

Copy link
Copy Markdown
Collaborator Author

⚠️ Automated Test Results: ENV ISSUE

The test environment encountered an issue that requires manual attention.

Details: Executor error: Command failed: git clone --depth 1 --branch bugfix/autodev-34-20260825080924845 git@github.com:Memtensor-AI/MemOS-Cloud-CLI.git /data/test-workspaces/3cd3efd8a4010a9b/repo
Cloning into '/data/test-workspaces/3cd3efd8a4010a9b/repo'...
kex_exchange_identification: Connection closed by remote host
Connection closed by UNKNOWN port 65535
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
Branch: bugfix/autodev-34-20260825080924845

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai:generated Generated or modified by AI | 由 AI 生成或修改 area:core MOS 编排层 / 框架底座 / 跨模块问题 status:in-progress Someone or AI is working on it | 人工或 AI 正在处理

Projects

None yet

Development

Successfully merging this pull request may close these issues.

memos add 写入成功但 list/get 查不到:user_id 作用域在写入侧被静默丢弃

1 participant