headroomlabs-ai/headroom · error · ValueError

Memory {new_memory_id} not found

Error message

Memory {new_memory_id} not found

What it means

Raised by detach_supersession when the new memory ID is not found in the memories table. Both edge endpoints must exist for the reciprocal edge check and the subsequent UPDATEs; a missing new endpoint aborts the transaction before any writes.

Source

Thrown at headroom/memory/adapters/sqlite.py:733

        untouched.
        """
        if old_memory_id == new_memory_id:
            raise ValueError("A memory cannot supersede itself")

        with self._get_conn() as conn:
            conn.execute("BEGIN IMMEDIATE")
            rows = conn.execute(
                "SELECT * FROM memories WHERE id IN (?, ?)",
                (old_memory_id, new_memory_id),
            ).fetchall()
            memories = {row["id"]: self._row_to_memory(row) for row in rows}
            old_memory = memories.get(old_memory_id)
            new_memory = memories.get(new_memory_id)

            if old_memory is None:
                raise ValueError(f"Memory {old_memory_id} not found")
            if new_memory is None:
                raise ValueError(f"Memory {new_memory_id} not found")
            if old_memory.superseded_by != new_memory_id or new_memory.supersedes != old_memory_id:
                raise ValueError(
                    f"Memories {old_memory_id} and {new_memory_id} do not form "
                    "a reciprocal supersession edge"
                )

            conn.execute(
                "UPDATE memories SET valid_until = NULL, superseded_by = NULL WHERE id = ?",
                (old_memory_id,),
            )
            conn.execute(
                "UPDATE memories SET supersedes = NULL WHERE id = ?",
                (new_memory_id,),
            )

        old_memory.valid_until = None
        old_memory.superseded_by = None
        new_memory.supersedes = None

View on GitHub (pinned to 322425c43b)

Solutions

  1. Confirm both memories exist before detaching; query by both IDs in one call.
  2. If the new memory is intentionally gone, clear the dangling superseded_by/supersedes columns directly as a data repair.
  3. Restore the missing row from backup if chain integrity matters.

Example fix

// before
await store.detach_supersession(old_id, new_id)

// after
rows = await store.get_many([old_id, new_id])
if rows.get(old_id) and rows.get(new_id):
    await store.detach_supersession(old_id, new_id)
else:
    logger.warning("endpoint missing; skipping detach")
Defensive patterns

Strategy: try-catch

Validate before calling

if await store.get(new_memory_id) is None:
    logger.warning("new endpoint missing; clearing dangling edge manually")
    return

Try / catch

try:
    old_m, new_m = await store.detach_supersession(old_id, new_id)
except ValueError as e:
    logger.warning("detach failed, likely dangling edge: %s", e)

Prevention

When it happens

Trigger: The new memory was hard-deleted but the old one still points at it (dangling superseded_by); passing the new memory before it was saved; ID typos in manual repair invocations.

Common situations: Partial deletes that broke chain integrity; repair tooling operating on half-migrated data; concurrent writes deleting one endpoint.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/82ff8d4693ef05f8. Report an issue: GitHub.