JuliusBrussee/caveman · error
forget begin
Error message
forget begin: %w
What it means
Forget() opens a SQLite transaction before reading and repairing lineage pointers. If db.Begin() fails, the deletion never starts and this wrapped error is returned. Begin failures almost always mean the underlying SQLite connection or database file is unusable (locked, closed, or I/O error).
Solutions
- Check for other processes holding the DB: `fuser <db-file>` or `lsof <db-file>`; close or kill stale cavemem/sqlite3 writers and retry.
- Verify disk space (`df -h`) and that the file/directory is writable; free space or fix permissions, then retry.
- If the store was closed in-process, reopen it with the store constructor before calling Forget.
- Enable a busy timeout in the SQLite DSN (e.g. `?_busy_timeout=5000`) so short-lived lock contention resolves instead of failing.
Example fix
// before: default DSN, concurrent writers cause immediate 'database is locked'
s, err := mem.Open("cavemem.db")
// after: give SQLite a busy timeout
s, err := mem.Open("file:cavemem.db?_busy_timeout=5000") Defensive patterns
Strategy: retry
Validate before calling
// Go: check the store is usable before calling Forget
func storeWritable(path string) error {
if fi, err := os.Stat(path); err != nil {
return err
} else if fi.Mode()&0200 == 0 {
return fmt.Errorf("db file not writable: %s", path)
}
return nil
} Type guard
null
Try / catch
var ok bool
var err error
for i := 0; i < 3; i++ {
ok, err = store.Forget(id)
if err == nil || !strings.Contains(err.Error(), "forget begin") {
break
}
time.Sleep(100 * time.Millisecond << i) // wait out transient lock
} Prevention
- Configure a busy timeout in the SQLite DSN (e.g. `?_busy_timeout=5000`) to absorb short lock contention.
- Run a single cavemem process (MCP server or CLI, not both writing) per database at a time.
- Monitor disk space on the volume hosting the DB.
- Never call Forget after Close(); keep store lifetime ownership explicit.
When it happens
Trigger: Calling Forget(id) (API, cavemem_forget MCP tool, or `cavemem forget` CLI) while the database file is locked by another long-running writer, the store was already Closed(), or the DB file/directory is unreadable or on a full disk.
Common situations: Two cavemem processes (CLI + MCP server) contending on the same database, a hung external sqlite3 shell holding a write lock, read-only filesystem or full disk, or calling Forget after Close.
Understand the failure class
Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.
Related errors
- file changed while securing
- forget commit
- forget
- memory changed during supersede
- repair forgotten predecessor
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/1c1bf116447eab3e.
Report an issue: GitHub.
Appendix: source
Thrown at mem/store.go:327
return nil, fmt.Errorf("broken supersession history at %s: %w", cursor.ID, err)
}
if seen[next.ID] {
return nil, fmt.Errorf("cyclic supersession history at %s", next.ID)
}
seen[next.ID] = true
history = append(history, next)
cursor = next
}
return history, nil
}
// Forget deletes a memory by id and atomically repairs neighboring lineage
// pointers. Expired predecessors stay expired: deleting a current version means
// forgetting that fact, never silently resurrecting an older one.
func (s *Store) Forget(id string) (bool, error) {
tx, err := s.db.Begin()
if err != nil {
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)View on GitHub (pinned to 3ee70a1026)