{"record":{"id":"82c16c0fa38164bb","repo":"JuliusBrussee/caveman","slug":"broken-supersession-history-at-s-w","errorCode":null,"errorMessage":"broken supersession history at %s: %w","messagePattern":"broken supersession history at (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mem/store.go","lineNumber":291,"sourceCode":"}\n\n// History returns the complete oldest-to-newest supersession chain containing\n// id. Broken/cyclic lineage is rejected rather than returning partial history.\nfunc (s *Store) History(id string) ([]Memory, error) {\n\tif strings.TrimSpace(id) == \"\" {\n\t\treturn nil, fmt.Errorf(\"history requires memory id\")\n\t}\n\tcurrent, err := s.memoryByID(id)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tseen := map[string]bool{current.ID: true}\n\tvar before []Memory\n\tcursor := current\n\tfor cursor.Supersedes != \"\" {\n\t\tprev, err := s.memoryByID(cursor.Supersedes)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"broken supersession history at %s: %w\", cursor.ID, err)\n\t\t}\n\t\tif seen[prev.ID] {\n\t\t\treturn nil, fmt.Errorf(\"cyclic supersession history at %s\", prev.ID)\n\t\t}\n\t\tseen[prev.ID] = true\n\t\tbefore = append(before, prev)\n\t\tcursor = prev\n\t}\n\thistory := make([]Memory, 0, len(before)+1)\n\tfor i := len(before) - 1; i >= 0; i-- {\n\t\thistory = append(history, before[i])\n\t}\n\thistory = append(history, current)\n\tcursor = current\n\tfor cursor.SupersededBy != \"\" {\n\t\tnext, err := s.memoryByID(cursor.SupersededBy)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"broken supersession history at %s: %w\", cursor.ID, err)","sourceCodeStart":273,"sourceCodeEnd":309,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/mem/store.go#L273-L309","documentation":"History() walks the supersession chain backwards from the requested memory via each record's Supersedes pointer. When the pointed-to predecessor cannot be loaded from SQLite (memoryByID fails, typically not-found), the walk aborts with this wrapped error naming the memory whose lineage pointer is dangling. The store fails closed rather than returning a partial history, since a truncated chain would misrepresent the fact's audit trail.","triggerScenarios":"Calling History(id) (directly, via the cavemem_history MCP tool, or the `cavemem history` CLI) where some ancestor in the chain was deleted without repairing lineage pointers — e.g. rows removed with raw SQL, a crashed/outdated external writer, or corruption from a non-transactional edit.","commonSituations":"Manual sqlite3 surgery on the cavemem database, restoring a partially backed-up DB file, older schema versions whose migration missed lineage repair, or a foreign writer deleting predecessor rows directly instead of going through Forget().","solutions":["Run History on a different (non-deleted) member of the chain, or Forget(id) the broken record so Forget repairs the neighbors' pointers.","Inspect the memories table around the reported id: `sqlite3 <db> \"SELECT id, supersedes, superseded_by FROM memories\"` and fix or delete rows with dangling supersedes values.","Delete the offending dangling row(s) with proper SQL that also updates neighbors' pointers, mimicking Forget()'s repair logic.","If the DB file is corrupted or partially restored, rebuild it: re-remember surviving facts into a fresh store."],"exampleFix":"// direct row deletion leaves a dangling pointer and breaks History\nsqlite3 cavemem.db \"DELETE FROM memories WHERE id='old-1';\"\n// use the API so lineage pointers are repaired atomically\ncavemem forget old-1\ncavemem history current-id","handlingStrategy":"try-catch","validationCode":"// Go: pre-check the chain's predecessors exist before calling History\nfunc chainIntact(db *sql.DB, id string) bool {\n\trow := db.QueryRow(`SELECT supersedes FROM memories WHERE id=?`, id)\n\tvar prev sql.NullString\n\tif row.Scan(&prev) != nil { return false }\n\tfor prev.Valid && prev.String != \"\" {\n\t\tvar next sql.NullString\n\t\tif db.QueryRow(`SELECT supersedes FROM memories WHERE id=?`, prev.String).Scan(&next) != nil {\n\t\t\treturn false // dangling predecessor\n\t\t}\n\t\tprev = next\n\t}\n\treturn true\n}","typeGuard":"null","tryCatchPattern":"hist, err := store.History(id)\nif err != nil {\n\tvar broken string\n\tif _, scanErr := fmt.Sscanf(err.Error(), \"broken supersession history at %s\", &broken); scanErr == nil {\n\t\t// repair lineage, e.g. store.Forget(broken), then retry once\n\t}\n\treturn fmt.Errorf(\"history unavailable for %s: %w\", id, err)\n}","preventionTips":["Never edit the cavemem SQLite file with raw SQL; use Remember/Supersede/Forget so lineage stays consistent.","Use Forget() for any deletion — it repairs neighbor pointers atomically.","Back up the DB only while no writer is active, and restore whole files, never partial row sets.","Run periodic `PRAGMA integrity_check;` and audit `supersedes` values that point to nonexistent ids."],"tags":["sqlite","data-integrity","lineage","history"],"backgroundTag":"record-not-found","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}