JuliusBrussee/caveman · error

forget commit

Error message

forget commit: %w

What it means

Forget() performs all lineage repairs and the row deletion inside one transaction and commits at the end. If tx.Commit() fails, nothing was persisted (the deferred rollback releases the work) and this wrapped error is returned. Commit failures typically mean the SQLite journal/WAL could not be finalized — disk full, I/O error, or connection closed.

Solutions

  1. Free disk space and check storage health (`df -h`, `dmesg`), then simply retry Forget — it is idempotent and re-checks the row.
  2. Ensure no other process swaps/deletes the DB file during operation (`lsof <db>`); pause backup jobs that move the file.
  3. Avoid calling Close() concurrently with Forget; coordinate shutdown so store operations finish first.
  4. On network filesystems, move the store to a local disk; SQLite locking on NFS is unreliable and can fail at commit.

Example fix

// before: concurrent Close while Forget commits -> 'forget commit: sql: database is closed'
go s.Close()
_, _ = s.Forget(id)
// after: serialize shutdown
ok, err := s.Forget(id)
if err != nil { return err }
s.Close()
Defensive patterns

Strategy: retry

Validate before calling

// Go: before Forget, confirm stable storage and no concurrent closer
func safeToForget(s *mem.Store, path string) error {
	if err := checkDiskSpace(path); err != nil { return err }
	if s.Closed() { return fmt.Errorf("store already closed") }
	return nil
}

Type guard

null

Try / catch

ok, err := store.Forget(id)
if err != nil && strings.Contains(err.Error(), "forget commit") {
	// work rolled back; fix storage condition (disk full, file moved, closed conn) and retry
	time.Sleep(500 * time.Millisecond)
	ok, err = store.Forget(id) // safe: Forget re-checks existence
}

Prevention

When it happens

Trigger: Calling Forget(id) when the disk fills or the I/O path fails exactly at commit time, the database file was replaced/removed mid-transaction, or the store's connection was closed concurrently by another goroutine.

Common situations: Volume hitting 100% during the delete burst, NFS/network filesystems with flaky locking, backup jobs that swap the DB file while cavemem holds an open transaction, or app shutdown calling Close() while Forget is in flight.

Related errors


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

Appendix: source

Thrown at mem/store.go:360

			`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
}

// Count returns the number of durable memory blocks without loading their
// contents. Callers omit this number on error rather than presenting zero.
func (s *Store) Count() (int, error) {
	var count int
	if err := s.db.QueryRow(`SELECT COUNT(*) FROM memories WHERE valid_until IS NULL`).Scan(&count); err != nil {
		return 0, fmt.Errorf("count memories: %w", err)
	}

View on GitHub (pinned to 3ee70a1026)