JuliusBrussee/caveman · error
store memory
Error message
store memory: %w
What it means
Remember() wraps failures from the INSERT OR IGNORE INTO memories statement (run through ccr.RetryOnBusy) with this error. It means the memory text could not be durably written to the SQLite store, so Remember returns no Memory. Because the raw text write is the first durable step, this failure means nothing was remembered.
Solutions
- Read the wrapped cause: 'database is locked' → reduce concurrent writers or increase retry tolerance; 'readonly database' → fix permissions/mount.
- Check free disk space and quotas.
- Verify the store was created via mem.Open so the schema exists.
- Back up and recreate the db if it is corrupt.
- Retry Remember after transient lock contention clears.
Example fix
// before
m, err := store.Remember(text)
if err != nil { panic(err) }
// after
m, err := store.Remember(text)
if err != nil {
if strings.Contains(err.Error(), "database is locked") {
time.Sleep(100 * time.Millisecond)
m, err = store.Remember(text) // bounded retry
}
if err != nil { return fmt.Errorf("remember %q: %w", text, err) }
} Defensive patterns
Strategy: retry
Validate before calling
// Go: confirm store is open and db file writable before Remember
if store == nil { return fmt.Errorf("store not open") } Try / catch
m, err := store.Remember(text)
if err != nil {
if strings.Contains(err.Error(), "database is locked") {
time.Sleep(backoff); m, err = store.Remember(text) // bounded retries
}
if err != nil { return err }
} Prevention
- Minimize concurrent writers to the same memory db
- Monitor disk space and file permissions
- Always construct the Store via mem.Open so schema exists
- Use bounded retry with backoff for transient SQLITE_BUSY
When it happens
Trigger: Calling Remember when the SQLite db is locked beyond the busy-retry budget, the disk is full, the db file is corrupt/read-only, or the schema is missing (e.g. store constructed without Open).
Common situations: Concurrent writers to the same memory db (SQLITE_BUSY), running from a read-only container filesystem, disk quota exhaustion, or a corrupted db after an unclean shutdown.
Related errors
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/8f48aa95d8ac34bd.
Report an issue: GitHub.
Appendix: source
Thrown at mem/store.go:185
if strings.TrimSpace(text) == "" {
return Memory{}, fmt.Errorf("cannot remember empty text")
}
if err := validateMemorySize(text); err != nil {
return Memory{}, err
}
id := memID(text)
created := time.Now().UTC().Format(time.RFC3339Nano)
// INSERT OR IGNORE keeps the original created_at on a repeat remember.
// RetryOnBusy: under a fan-out of cavemem processes, a queued writer can
// outlast a single busy_timeout (Windows file locking is the slowest case).
if err := ccr.RetryOnBusy(func() error {
_, e := s.db.Exec(
`INSERT OR IGNORE INTO memories (id, text, created_at, valid_from) VALUES (?, ?, ?, ?)`,
id, text, created, created,
)
return e
}); err != nil {
return Memory{}, fmt.Errorf("store memory: %w", err)
}
var stored Memory
row := s.db.QueryRow(memorySelect+` WHERE id = ?`, id)
if err := scanMemory(row, &stored); err != nil {
return Memory{}, fmt.Errorf("read back memory: %w", err)
}
if stored.ValidUntil != nil {
return Memory{}, fmt.Errorf("memory %s exists only as an expired historical version and cannot be re-remembered as current", stored.ID)
}
return stored, nil
}
// Supersede atomically replaces one current memory with a new current version.
// The old row remains durable for history and byte-exact audit, but normal recall
// excludes it. An already-expired or unknown id fails closed.
func (s *Store) Supersede(oldID, newText string) (Memory, error) {
if strings.TrimSpace(oldID) == "" {
return Memory{}, fmt.Errorf("supersede requires old memory id")View on GitHub (pinned to 3ee70a1026)