{"record":{"id":"7467698e2a5fbaa0","repo":"JuliusBrussee/caveman","slug":"forget-w","errorCode":null,"errorMessage":"forget: %w","messagePattern":"forget: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mem/store.go","lineNumber":357,"sourceCode":"\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}\n\nfunc nullableLineageID(value sql.NullString) any {\n\tif value.Valid {\n\t\treturn value.String\n\t}\n\treturn nil\n}\n\n// Count returns the number of durable memory blocks without loading their\n// contents. Callers omit this number on error rather than presenting zero.\nfunc (s *Store) Count() (int, error) {\n\tvar count int","sourceCodeStart":339,"sourceCodeEnd":375,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/mem/store.go#L339-L375","documentation":"After repairing both neighbor pointers, Forget() deletes the target row with `DELETE FROM memories WHERE id = ?`. If that statement fails, the transaction (including all pointer repairs) is rolled back and this wrapped error is returned. This is the final write of the forget; failures point to storage-level problems.","triggerScenarios":"Calling Forget(id) on an existing memory when the database has become unwritable between the earlier reads and the DELETE: read-only filesystem flipped to read-only, disk full, lock contention, or page corruption. (A row that vanished earlier surfaces as (false, nil), not this error.)","commonSituations":"Long-running MCP server whose volume filled mid-operation, an admin chmod-ing the DB read-only while the server runs, or concurrent writers exhausting SQLite's lock handling.","solutions":["Check the filesystem is writable and has space (`df -h`, `touch <dir>/test`), fix, and retry the forget.","Check for competing writers/locks with `lsof <db>`; serialize deletes or add a busy timeout to the DSN.","Run `PRAGMA integrity_check;` — on corruption, restore from backup and rebuild lineage via remember/supersede.","Retry Forget after conditions change; note Forget is idempotent (missing id returns false, nil), so a safe re-run is cheap."],"exampleFix":"// before: transient lock causes DELETE to fail; immediate hard fail\nok, err := s.Forget(id)\n// after: open store with busy timeout so SQLite waits out short locks\ns, err := mem.Open(\"file:cavemem.db?_busy_timeout=5000\")\n// then re-run Forget; it is safe to retry\nok, err := s.Forget(id)","handlingStrategy":"retry","validationCode":"// Go: idempotency makes retry safe; pre-check storage writability first\nfunc canRetryForget(store *mem.Store, id string) bool {\n\t_, err := store.History(id) // chain errors mean the row state, not storage, is the issue\n\treturn err == nil || !strings.Contains(err.Error(), \"broken supersession history\")\n}","typeGuard":"null","tryCatchPattern":"for attempt := 0; attempt < 3; attempt++ {\n\tok, err := store.Forget(id)\n\tif err == nil {\n\t\t_ = ok\n\t\tbreak\n\t}\n\tif !strings.Contains(err.Error(), \"forget:\") {\n\t\tbreak // different failure, don't blind-retry\n\t}\n\ttime.Sleep(time.Duration(attempt+1) * 200 * time.Millisecond)\n}","preventionTips":["Treat Forget as idempotent and safe to re-run — build retry logic around that guarantee.","Avoid external jobs that flip the DB file or volume read-only while cavemem runs.","Set WAL journal mode plus a busy timeout for resilient concurrent access.","Watch disk space and inode health on the store volume."],"tags":["sqlite","delete","write-failed","transaction"],"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"}