Stop a tool-context memory rename from aborting on read-only memories (#2081)

Rename propagation
* `rename_memory_and_propagate_references` enumerated `get_full_list()`, which
  includes the memories matched by `read_only_memory_patterns`.
* Writing one of those raises `PermissionError` in `_check_write_access`, so a
  rename of a memory that was referenced from a read-only memory failed *after*
  `move_memory` had already applied the rename.
* The memory graph was then half-updated: the renamed memory existed under its
  new name while the writable referrers still held `mem:OLD_NAME`, and retrying
  the rename failed with "Memory not found".

Fix
* In a tool context, propagate only into the memories that accept writes, sorted
  to keep the enumeration order that `get_full_list()` provided.
* Outside a tool context nothing changes, so read-only memories are still updated
  when the user renames through the CLI.
* A reference which thereby remains in a read-only memory is still reported as
  stale by `validate_referential_integrity`, so it is not hidden.

Documentation
* `docs/02-usage/045_memories.md` promised that the tool rewrites every reference across all
  memories. That is now only true for the memories the agent may write, so the caveat is stated
  where the promise is made.

Tests
* Add a regression pair: the tool-context rename completes and rewrites both
  occurrences in a writable referrer, while the CLI context additionally rewrites
  the read-only one.
This commit is contained in:
fei authored and GitHub committed 2026-09-21 20:52:22 +02:00
1 parent d80da2cdc6
commit 24068f7b31
4 files changed
+60 -6

No files matched your search

+38
View File
@@ -844,3 +844,41 @@ class TestAutoPrefixBareReferences:
# idempotent: the second run should not touch anything
assert second.total_replacements == 0
assert fs_manager.load_memory("docs") == "the mem:auth/login process"
class TestRenameMemorySparesReadOnlyMemories:
"""Regression: a tool-context rename enumerated read-only memories, so propagating the
reference into one raised ``PermissionError`` after the rename itself had already been applied.
"""
@staticmethod
def _manager(tmp_path, monkeypatch) -> MemoryManager:
manager = MemoryManager(serena_data_folder=tmp_path, read_only_memory_patterns=[r"frozen/.*"])
# the global memories of the machine would otherwise join the enumeration as well
global_dir = tmp_path / "global"
global_dir.mkdir()
monkeypatch.setattr(manager, "_global_memory_dir", global_dir)
_write(manager, "auth/login", "# login notes")
_write(manager, "frozen/notes", "see `mem:auth/login`")
_write(manager, "docs", "first `mem:auth/login`, then `mem:auth/login`")
return manager
def test_tool_context_rename_completes_and_leaves_read_only_reference_alone(self, tmp_path, monkeypatch) -> None:
manager = self._manager(tmp_path, monkeypatch)
message, n_updated = manager.rename_memory_and_propagate_references("auth/login", "auth/signin", is_tool_context=True)
assert "auth/signin" in message
assert manager.load_memory("auth/signin") == "# login notes"
assert manager.load_memory("docs") == "first `mem:auth/signin`, then `mem:auth/signin`"
assert manager.load_memory("frozen/notes") == "see `mem:auth/login`"
assert n_updated == 2
def test_cli_context_rename_still_propagates_into_read_only_memories(self, tmp_path, monkeypatch) -> None:
manager = self._manager(tmp_path, monkeypatch)
_, n_updated = manager.rename_memory_and_propagate_references("auth/login", "auth/signin", is_tool_context=False)
assert manager.load_memory("frozen/notes") == "see `mem:auth/signin`"
assert manager.load_memory("docs") == "first `mem:auth/signin`, then `mem:auth/signin`"
assert n_updated == 3