JuliusBrussee/caveman · error

repair forgotten successor

Error message

repair forgotten successor: %w

What it means

When the forgotten memory has a successor (superseded_by is set), Forget() rewrites the successor's supersedes pointer (usually to NULL) so the chain skips the deleted record. A failure of this UPDATE rolls back the transaction and is wrapped as this error. As with the predecessor repair, this is a SQLite write failure, not a lineage-logic error.

Solutions

  1. Verify write access to the DB and its -wal/-shm files; fix ownership/permissions and retry.
  2. Free disk space if `df -h` shows the volume full, then retry the forget.
  3. Check `lsof <db>` for other writers; stop them or add a busy timeout (`?_busy_timeout=5000`) to ride out short locks.
  4. If corruption is suspected (`PRAGMA integrity_check` fails), restore from a clean backup; stop writers before copying DB files.

Example fix

# before: cleanup fails on a full disk
$ cavemem forget old-version-id
# forget: repair forgotten successor: database or disk is full
# after: free space, then retry
$ df -h /data && cavemem forget old-version-id
Defensive patterns

Strategy: retry

Validate before calling

// Go: verify free disk space before write-heavy store operations
const minFreeBytes = 10 << 20
func hasDiskSpace(path string) bool {
	var st syscall.Statfs_t
	if syscall.Statfs(filepath.Dir(path), &st) != nil { return false }
	return int64(st.Bavail)*int64(st.Bsize) > minFreeBytes
}

Type guard

null

Try / catch

ok, err := store.Forget(id)
if err != nil && strings.Contains(err.Error(), "repair forgotten successor") {
	// fix storage condition (space/permissions/lock) then retry; transaction rolled back cleanly
	return retryForget(store, id, 3)
}

Prevention

When it happens

Trigger: Calling Forget(id) on a superseded (older) version while the database is read-only or full, the write lock is held by another process, or the affected pages are corrupted.

Common situations: Forgetting history entries from a DB opened on a read-only volume, a full disk during cleanup of old versions, two processes deleting simultaneously, or corrupted WAL sidecar files blocking writes.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/a9b62aa98ea10db7. Report an issue: GitHub.

Appendix: source

Thrown at mem/store.go:353

		if err == sql.ErrNoRows {
			return false, nil
		}
		return false, fmt.Errorf("read forgotten memory: %w", err)
	}
	if supersedes.Valid {
		if _, err := tx.Exec(
			`UPDATE memories SET superseded_by = ? WHERE id = ?`,
			nullableLineageID(supersededBy), supersedes.String,
		); err != nil {
			return false, fmt.Errorf("repair forgotten predecessor: %w", err)
		}
	}
	if supersededBy.Valid {
		if _, err := tx.Exec(
			`UPDATE memories SET supersedes = ? WHERE id = ?`,
			nullableLineageID(supersedes), supersededBy.String,
		); err != nil {
			return false, fmt.Errorf("repair forgotten successor: %w", err)
		}
	}
	if _, err := tx.Exec(`DELETE FROM memories WHERE id = ?`, id); err != nil {
		return false, fmt.Errorf("forget: %w", err)
	}
	if err := tx.Commit(); err != nil {
		return false, fmt.Errorf("forget commit: %w", err)
	}
	return true, nil
}

func nullableLineageID(value sql.NullString) any {
	if value.Valid {
		return value.String
	}
	return nil
}

View on GitHub (pinned to 3ee70a1026)