{"record":{"id":"e5742b09cc9304bc","repo":"JuliusBrussee/caveman","slug":"cyclic-supersession-history-at-s","errorCode":null,"errorMessage":"cyclic supersession history at %s","messagePattern":"cyclic supersession history at (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mem/store.go","lineNumber":294,"sourceCode":"// 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)\n\t\t}\n\t\tif seen[next.ID] {\n\t\t\treturn nil, fmt.Errorf(\"cyclic supersession history at %s\", next.ID)","sourceCodeStart":276,"sourceCodeEnd":312,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/mem/store.go#L276-L312","documentation":"During History's backward walk over the Supersedes chain, a previously visited memory id was reached again, which means the lineage graph contains a cycle (A supersedes B, B supersedes A, or a self-loop). History refuses to return partial or infinite history and errors out at the offending id instead of looping forever.","triggerScenarios":"Corrupted lineage from manual SQLite edits, a partial bug in an older supersede implementation, or direct writes that set supersedes pointers inconsistently. The `seen` map trips when cursor.Supersedes resolves to an id already on the path — including the starting memory (self-reference).","commonSituations":"Hand-crafted rows inserted with sqlite3 for migration/testing that accidentally cross-link; concurrent supersede bugs in custom forks; restoring a backup on top of an existing table producing duplicate ids with stale pointers.","solutions":["Inspect the cycle: SELECT id, supersedes, superseded_by FROM memories WHERE id IN (...) following the supersedes chain from the id you passed to find the loop","Repair the bad pointer(s) — set the offending supersedes to the true predecessor (or NULL for the root) — the supersede transaction normally makes cycles impossible, so a cycle indicates manual/direct writes","If reproduction is easy, check whether any code path writes supersedes/superseded_by outside Store.Supersede and remove it"],"exampleFix":"-- before (corrupt row creating a cycle)\n-- A.supersedes = B, B.supersedes = A\n\n-- after: break the loop at the older memory\nUPDATE memories SET supersedes = NULL WHERE id = 'B';","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"hist, err := store.History(id)\nif err != nil {\n    if strings.Contains(err.Error(), \"cyclic supersession history\") {\n        // log the id, quarantine the chain, alert — data repair needed\n    }\n    return err\n}","preventionTips":["Never write supersedes/superseded_by outside Store.Supersede's transaction","After manual DB surgery or restore, run a History walk over affected ids to verify linearity","Keep SQLite backups before any direct pointer edits so a bad repair is reversible"],"tags":["go","data-corruption","cycle-detection","memory-store"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}