JuliusBrussee/caveman · error
broken supersession history at
Error message
broken supersession history at %s: %w
What it means
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.
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.
Example fix
// direct row deletion leaves a dangling pointer and breaks History sqlite3 cavemem.db "DELETE FROM memories WHERE id='old-1';" // use the API so lineage pointers are repaired atomically cavemem forget old-1 cavemem history current-id
Defensive patterns
Strategy: try-catch
Validate before calling
// Go: pre-check the chain's predecessors exist before calling History
func chainIntact(db *sql.DB, id string) bool {
row := db.QueryRow(`SELECT supersedes FROM memories WHERE id=?`, id)
var prev sql.NullString
if row.Scan(&prev) != nil { return false }
for prev.Valid && prev.String != "" {
var next sql.NullString
if db.QueryRow(`SELECT supersedes FROM memories WHERE id=?`, prev.String).Scan(&next) != nil {
return false // dangling predecessor
}
prev = next
}
return true
} Type guard
null
Try / catch
hist, err := store.History(id)
if err != nil {
var broken string
if _, scanErr := fmt.Sscanf(err.Error(), "broken supersession history at %s", &broken); scanErr == nil {
// repair lineage, e.g. store.Forget(broken), then retry once
}
return fmt.Errorf("history unavailable for %s: %w", id, err)
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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().
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- cyclic supersession history at
- repair forgotten successor
- budget is below CCR storage minimum
- cave_ccr_budget_exceeded
- cave_tool_result_recovery_mismatch
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/82c16c0fa38164bb.
Report an issue: GitHub.
Appendix: source
Thrown at mem/store.go:291
}
// History returns the complete oldest-to-newest supersession chain containing
// id. Broken/cyclic lineage is rejected rather than returning partial history.
func (s *Store) History(id string) ([]Memory, error) {
if strings.TrimSpace(id) == "" {
return nil, fmt.Errorf("history requires memory id")
}
current, err := s.memoryByID(id)
if err != nil {
return nil, err
}
seen := map[string]bool{current.ID: true}
var before []Memory
cursor := current
for cursor.Supersedes != "" {
prev, err := s.memoryByID(cursor.Supersedes)
if err != nil {
return nil, fmt.Errorf("broken supersession history at %s: %w", cursor.ID, err)
}
if seen[prev.ID] {
return nil, fmt.Errorf("cyclic supersession history at %s", prev.ID)
}
seen[prev.ID] = true
before = append(before, prev)
cursor = prev
}
history := make([]Memory, 0, len(before)+1)
for i := len(before) - 1; i >= 0; i-- {
history = append(history, before[i])
}
history = append(history, current)
cursor = current
for cursor.SupersededBy != "" {
next, err := s.memoryByID(cursor.SupersededBy)
if err != nil {
return nil, fmt.Errorf("broken supersession history at %s: %w", cursor.ID, err)View on GitHub (pinned to 3ee70a1026)