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

  1. Run History on a different (non-deleted) member of the chain, or Forget(id) the broken record so Forget repairs the neighbors' pointers.
  2. 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.
  3. Delete the offending dangling row(s) with proper SQL that also updates neighbors' pointers, mimicking Forget()'s repair logic.
  4. 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

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


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)