{"record":{"id":"6a916785e15eec84","repo":"JuliusBrussee/caveman","slug":"forget-commit-w","errorCode":null,"errorMessage":"forget commit: %w","messagePattern":"forget commit: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mem/store.go","lineNumber":360,"sourceCode":"\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\n\tif err := s.db.QueryRow(`SELECT COUNT(*) FROM memories WHERE valid_until IS NULL`).Scan(&count); err != nil {\n\t\treturn 0, fmt.Errorf(\"count memories: %w\", err)\n\t}","sourceCodeStart":342,"sourceCodeEnd":378,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/mem/store.go#L342-L378","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Free disk space and check storage health (`df -h`, `dmesg`), then simply retry Forget — it is idempotent and re-checks the row.","Ensure no other process swaps/deletes the DB file during operation (`lsof <db>`); pause backup jobs that move the file.","Avoid calling Close() concurrently with Forget; coordinate shutdown so store operations finish first.","On network filesystems, move the store to a local disk; SQLite locking on NFS is unreliable and can fail at commit."],"exampleFix":"// before: concurrent Close while Forget commits -> 'forget commit: sql: database is closed'\ngo s.Close()\n_, _ = s.Forget(id)\n// after: serialize shutdown\nok, err := s.Forget(id)\nif err != nil { return err }\ns.Close()","handlingStrategy":"retry","validationCode":"// Go: before Forget, confirm stable storage and no concurrent closer\nfunc safeToForget(s *mem.Store, path string) error {\n\tif err := checkDiskSpace(path); err != nil { return err }\n\tif s.Closed() { return fmt.Errorf(\"store already closed\") }\n\treturn nil\n}","typeGuard":"null","tryCatchPattern":"ok, err := store.Forget(id)\nif err != nil && strings.Contains(err.Error(), \"forget commit\") {\n\t// work rolled back; fix storage condition (disk full, file moved, closed conn) and retry\n\ttime.Sleep(500 * time.Millisecond)\n\tok, err = store.Forget(id) // safe: Forget re-checks existence\n}","preventionTips":["Host the store on local, fsync-reliable storage; avoid NFS for SQLite.","Coordinate shutdown: never call Close() while other goroutines may be mid-Forget.","Prevent backup tooling from moving or replacing the DB file during operation.","Alert on disk-full conditions; commit is the write most likely to fail when the volume fills."],"tags":["sqlite","commit","transaction","disk"],"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"}