{"record":{"id":"5bbd420d3d6790cb","repo":"JuliusBrussee/caveman","slug":"repair-forgotten-predecessor-w","errorCode":null,"errorMessage":"repair forgotten predecessor: %w","messagePattern":"repair forgotten predecessor: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mem/store.go","lineNumber":345,"sourceCode":"\t\treturn false, fmt.Errorf(\"forget begin: %w\", err)\n\t}\n\tdefer tx.Rollback()\n\n\tvar supersedes, supersededBy sql.NullString\n\tif err := tx.QueryRow(\n\t\t`SELECT supersedes, superseded_by FROM memories WHERE id = ?`, id,\n\t).Scan(&supersedes, &supersededBy); err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, fmt.Errorf(\"read forgotten memory: %w\", err)\n\t}\n\tif supersedes.Valid {\n\t\tif _, err := tx.Exec(\n\t\t\t`UPDATE memories SET superseded_by = ? WHERE id = ?`,\n\t\t\tnullableLineageID(supersededBy), supersedes.String,\n\t\t); err != nil {\n\t\t\treturn false, fmt.Errorf(\"repair forgotten predecessor: %w\", err)\n\t\t}\n\t}\n\tif supersededBy.Valid {\n\t\tif _, err := tx.Exec(\n\t\t\t`UPDATE memories SET supersedes = ? WHERE id = ?`,\n\t\t\tnullableLineageID(supersedes), supersededBy.String,\n\t\t); err != nil {\n\t\t\treturn false, fmt.Errorf(\"repair forgotten successor: %w\", err)\n\t\t}\n\t}\n\tif _, err := tx.Exec(`DELETE FROM memories WHERE id = ?`, id); err != nil {\n\t\treturn false, fmt.Errorf(\"forget: %w\", err)\n\t}\n\tif err := tx.Commit(); err != nil {\n\t\treturn false, fmt.Errorf(\"forget commit: %w\", err)\n\t}\n\treturn true, nil\n}","sourceCodeStart":327,"sourceCodeEnd":363,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/mem/store.go#L327-L363","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check writability: ensure the DB file, its directory, and -wal/-shm files are writable by the current user; fix permissions and retry.","Free disk space / raise quota (`df -h`) if the disk is full, then retry Forget.","Look for competing writers holding the write lock (`lsof <db>`) and stop them; add `_busy_timeout` to the DSN for transient contention.","Run `PRAGMA integrity_check;`; if pages are corrupt, restore from backup and replay via remember/supersede."],"exampleFix":"// before: DB on a read-only backup mount -> repair UPDATE fails\ns, _ := mem.Open(\"/mnt/backup-ro/cavemem.db\")\ns.Forget(id) // repair forgotten predecessor: attempt to write a readonly database\n// after: open a writable copy\ns, _ := mem.Open(\"/data/cavemem.db\")","handlingStrategy":"retry","validationCode":"// Go: ensure the DB volume is writable and has headroom before Forget\nfunc writableCheck(path string) error {\n\tif err := unix.Access(filepath.Dir(path), unix.W_OK); err != nil {\n\t\treturn fmt.Errorf(\"db directory not writable: %w\", err)\n\t}\n\treturn nil\n}","typeGuard":"null","tryCatchPattern":"ok, err := store.Forget(id)\nif err != nil && strings.Contains(err.Error(), \"repair forgotten predecessor\") {\n\t// check read-only mount / disk full / lock, fix, then retry (Forget is idempotent)\n\tif errors.Is(err, sqlite.ErrBusy) { /* retry with backoff */ }\n\treturn err\n}","preventionTips":["Never open the store from a read-only mount or backup copy that must stay pristine.","Keep the DB, its directory, and -wal/-shm files owned and writable by the service user.","Alert on disk usage thresholds so the volume never fills mid-transaction.","Set a busy timeout in the DSN to tolerate concurrent writers."],"tags":["sqlite","write-failed","transaction","permissions"],"backgroundTag":"database-write-failed","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}