{"record":{"id":"2bc9e802b0cca28a","repo":"oraios/serena","slug":"attempted-to-write-to-read-only-memory-name","errorCode":null,"errorMessage":"Attempted to write to read_only memory: '{name}')","messagePattern":"Attempted to write to read_only memory: '(.+?)'\\)","errorType":"exception","errorClass":"PermissionError","httpStatus":null,"severity":"error","filePath":"src/serena/memories/memory_manager.py","lineNumber":202,"sourceCode":"            raise ValueError(f\"Memory name cannot be absolute or contain empty path segments. Got: {name}\")\n\n        if self._is_global(name):\n            if name == self.GLOBAL_TOPIC:\n                raise ValueError(\n                    f'Bare \"{self.GLOBAL_TOPIC}\" is not a valid memory name. Use \"{self.GLOBAL_TOPIC}/<name>\" to address a global memory.'\n                )\n            # Strip \"global/\" prefix and resolve against global dir\n            sub_name = name[len(self.GLOBAL_TOPIC) + 1 :]\n            return self._resolve_memory_path(self._global_memory_dir, sub_name.split(\"/\"))\n\n        # Project-local memory\n        assert self._project_memory_dir is not None, \"Project dir was not passed at initialization\"\n        return self._resolve_memory_path(self._project_memory_dir, parts)\n\n    def _check_write_access(self, name: str, is_tool_context: bool) -> None:\n        # in tool context, memories can be read-only\n        if is_tool_context and self._is_read_only_memory(name):\n            raise PermissionError(f\"Attempted to write to read_only memory: '{name}')\")\n\n    def load_memory(self, name: str) -> str:\n        name = self._sanitize_name(name)\n        self._check_not_ignored(name)\n        memory_file_path = self.get_memory_file_path(name)\n        if not memory_file_path.exists():\n            raise FileNotFoundError(f\"Memory named '{name}' not found\")\n        with open(memory_file_path, encoding=self._encoding) as f:\n            return f.read()\n\n    def save_memory(self, name: str, content: str, is_tool_context: bool) -> str:\n        name = self._sanitize_name(name)\n        self._check_not_ignored(name)\n        self._check_write_access(name, is_tool_context)\n        memory_file_path = self.get_memory_file_path(name)\n        with open(memory_file_path, \"w\", encoding=self._encoding) as f:\n            f.write(content)\n        return f\"Memory {name} written.\"","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/oraios/serena/blob/7fcbca7e62555ec2287ddb2f083caee805848ea6/src/serena/memories/memory_manager.py#L184-L220","documentation":"_check_write_access raises PermissionError when a write operation targets a memory that matches a read_only_memory_patterns regex while operating in a tool-execution context (is_tool_context=True). Read-only memories can be read but not modified by tools, protecting curated/global content from agent-driven changes.","triggerScenarios":"Calling save_memory, delete_memory, move_memory, or edit_memory with is_tool_context=True on a name that regex-fullmatches any read_only_memory_patterns entry configured on the MemoryManager.","commonSituations":"An agent (via write_memory/edit_memory tools) attempts to overwrite protected memories such as global conventions or system-maintained notes; a recently added read_only pattern now covers names that tools previously could edit; renaming a memory whose destination name matches a read-only pattern.","solutions":["Perform the write outside tool context: call the API with is_tool_context=False from non-tool code.","Adjust read_only_memory_patterns in the Serena config if the memory should be tool-writable.","Have a human edit the memory file directly rather than through the tool context.","Choose a non-protected destination name when the intent was move_memory/rename."],"exampleFix":"// before\nmanager.save_memory(\"global/conventions\", content, is_tool_context=True)\n// PermissionError\n// after\nmanager.save_memory(\"global/conventions\", content, is_tool_context=False)  # non-tool context\n# or relax config: read_only_memory_patterns = [\"global/scratch/.*\"]","handlingStrategy":"validation","validationCode":"import re\nif is_tool_context and any(p.fullmatch(name) for p in map(re.compile, read_only_memory_patterns)):\n    raise PermissionError(f\"{name} is read-only in tool context\")","typeGuard":"def is_writable(name: str, read_only: list[re.Pattern], is_tool_context: bool) -> bool:\n    return not (is_tool_context and any(p.fullmatch(name) for p in read_only))","tryCatchPattern":"try:\n    manager.save_memory(name, content, is_tool_context=True)\nexcept PermissionError:\n    log.info(\"%s is read-only in tool context; editing outside tool context\", name)\n    manager.save_memory(name, content, is_tool_context=False)","preventionTips":["Keep a manifest of read-only memory names and skip them in agent write workflows","Review read_only_memory_patterns when adding new protected memories","Surface read-only status to the agent in prompts to avoid wasted write attempts"],"tags":["python","memory","permissions","configuration"],"backgroundTag":"read-only-memory-write-denied","analyzedSha":"7fcbca7e62555ec2287ddb2f083caee805848ea6","analyzedAt":"2026-08-29T00:04:09.619Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}