JuliusBrussee/caveman · error

repair forgotten predecessor

Error message

repair forgotten predecessor: %w

What it means

When the forgotten memory has a predecessor (supersedes is set), Forget() updates that predecessor's superseded_by pointer (usually to NULL) inside the same transaction. If that UPDATE fails, the whole forget rolls back and this wrapped error is returned. It signals a write failure on the SQLite file, not a data-logic problem.

Solutions

  1. Check writability: ensure the DB file, its directory, and -wal/-shm files are writable by the current user; fix permissions and retry.
  2. Free disk space / raise quota (`df -h`) if the disk is full, then retry Forget.
  3. Look for competing writers holding the write lock (`lsof <db>`) and stop them; add `_busy_timeout` to the DSN for transient contention.
  4. Run `PRAGMA integrity_check;`; if pages are corrupt, restore from backup and replay via remember/supersede.

Example fix

// before: DB on a read-only backup mount -> repair UPDATE fails
s, _ := mem.Open("/mnt/backup-ro/cavemem.db")
s.Forget(id) // repair forgotten predecessor: attempt to write a readonly database
// after: open a writable copy
s, _ := mem.Open("/data/cavemem.db")
Defensive patterns

Strategy: retry

Validate before calling

// Go: ensure the DB volume is writable and has headroom before Forget
func writableCheck(path string) error {
	if err := unix.Access(filepath.Dir(path), unix.W_OK); err != nil {
		return fmt.Errorf("db directory not writable: %w", err)
	}
	return nil
}

Type guard

null

Try / catch

ok, err := store.Forget(id)
if err != nil && strings.Contains(err.Error(), "repair forgotten predecessor") {
	// check read-only mount / disk full / lock, fix, then retry (Forget is idempotent)
	if errors.Is(err, sqlite.ErrBusy) { /* retry with backoff */ }
	return err
}

Prevention

When it happens

Trigger: Calling Forget(id) on a memory that has a predecessor while the database is read-only, the disk is full, the file is locked by another writer at UPDATE time, or the DB is corrupted on the affected pages.

Common situations: Running cavemem against a DB on a read-only mount or read-only backup copy, disk quota exhausted, concurrent long write transactions holding the lock, or WAL/journal files not writable.

Related errors


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

Appendix: source

Thrown at mem/store.go:345

		return false, fmt.Errorf("forget begin: %w", err)
	}
	defer tx.Rollback()

	var supersedes, supersededBy sql.NullString
	if err := tx.QueryRow(
		`SELECT supersedes, superseded_by FROM memories WHERE id = ?`, id,
	).Scan(&supersedes, &supersededBy); err != nil {
		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
}

View on GitHub (pinned to 3ee70a1026)