{"record":{"id":"ae7058d7458def41","repo":"headroomlabs-ai/headroom","slug":"a-memory-cannot-supersede-itself","errorCode":null,"errorMessage":"A memory cannot supersede itself","messagePattern":"A memory cannot supersede itself","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"headroom/memory/adapters/sqlite.py","lineNumber":718,"sourceCode":"                row,\n            )\n            conn.commit()\n\n        return new_memory\n\n    async def detach_supersession(\n        self,\n        old_memory_id: str,\n        new_memory_id: str,\n    ) -> tuple[Memory, Memory]:\n        \"\"\"Atomically detach one verified supersession edge.\n\n        This is an explicit repair operation. It never infers identity from\n        content or embedding similarity and leaves neighboring chain edges\n        untouched.\n        \"\"\"\n        if old_memory_id == new_memory_id:\n            raise ValueError(\"A memory cannot supersede itself\")\n\n        with self._get_conn() as conn:\n            conn.execute(\"BEGIN IMMEDIATE\")\n            rows = conn.execute(\n                \"SELECT * FROM memories WHERE id IN (?, ?)\",\n                (old_memory_id, new_memory_id),\n            ).fetchall()\n            memories = {row[\"id\"]: self._row_to_memory(row) for row in rows}\n            old_memory = memories.get(old_memory_id)\n            new_memory = memories.get(new_memory_id)\n\n            if old_memory is None:\n                raise ValueError(f\"Memory {old_memory_id} not found\")\n            if new_memory is None:\n                raise ValueError(f\"Memory {new_memory_id} not found\")\n            if old_memory.superseded_by != new_memory_id or new_memory.supersedes != old_memory_id:\n                raise ValueError(\n                    f\"Memories {old_memory_id} and {new_memory_id} do not form \"","sourceCodeStart":700,"sourceCodeEnd":736,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/memory/adapters/sqlite.py#L700-L736","documentation":"Raised by detach_supersession when old_memory_id equals new_memory_id. A self-edge would mean a memory supersedes itself, which is logically invalid, and detaching it is a no-op at best and graph corruption at worst, so it is rejected up front.","triggerScenarios":"Both parameters filled from the same variable (e.g. superseded_by and supersedes accidentally swapped or duplicated); programmatic edge lists generated with a copy-paste bug producing (id, id) pairs.","commonSituations":"Loop constructing edge pairs where old and new collapse to the same value; defensive repair scripts iterating IDs that accidentally pair a memory with itself.","solutions":["Skip pairs where old == new before calling detach_supersession.","Fix the upstream variable assignment that produces identical IDs.","Treat this error as a symptom of a corrupted edge list and audit how the pairs were generated."],"exampleFix":"// before\nawait store.detach_supersession(mem.id, mem.id)\n\n// after\nif old_id != new_id:\n    await store.detach_supersession(old_id, new_id)","handlingStrategy":"validation","validationCode":"if old_memory_id == new_memory_id:\n    raise ValueError(\"self-supersession attempted\")\nawait store.detach_supersession(old_memory_id, new_memory_id)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate edge pairs (old != new) when generating repair lists.","Add unit tests covering degenerate (id, id) pairs in edge-processing code."],"tags":["sqlite","supersession","self-reference","validation"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}