{"record":{"id":"9c179b16e3c277a3","repo":"JuliusBrussee/caveman","slug":"check-replacement-memory-w","errorCode":null,"errorMessage":"check replacement memory: %w","messagePattern":"check replacement memory: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mem/store.go","lineNumber":230,"sourceCode":"\tif err != nil {\n\t\treturn Memory{}, fmt.Errorf(\"supersede begin: %w\", err)\n\t}\n\tdefer tx.Rollback()\n\n\tvar old Memory\n\tif err := scanMemory(tx.QueryRow(memorySelect+` WHERE id = ? AND valid_until IS NULL`, oldID), &old); err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\treturn Memory{}, fmt.Errorf(\"memory %s is not current\", oldID)\n\t\t}\n\t\treturn Memory{}, fmt.Errorf(\"read current memory: %w\", err)\n\t}\n\tif old.Text == newText {\n\t\treturn Memory{}, fmt.Errorf(\"replacement text is identical to current memory\")\n\t}\n\tnewID := memID(newText)\n\tvar exists int\n\tif err := tx.QueryRow(`SELECT COUNT(*) FROM memories WHERE id = ?`, newID).Scan(&exists); err != nil {\n\t\treturn Memory{}, fmt.Errorf(\"check replacement memory: %w\", err)\n\t}\n\tif exists > 0 {\n\t\treturn Memory{}, fmt.Errorf(\"replacement memory %s already exists\", newID)\n\t}\n\tnow := time.Now().UTC().Format(time.RFC3339Nano)\n\tif _, err := tx.Exec(\n\t\t`INSERT INTO memories\n\t\t   (id, text, created_at, valid_from, supersedes)\n\t\t VALUES (?, ?, ?, ?, ?)`,\n\t\tnewID, newText, now, now, oldID,\n\t); err != nil {\n\t\treturn Memory{}, fmt.Errorf(\"insert replacement memory: %w\", err)\n\t}\n\tres, err := tx.Exec(\n\t\t`UPDATE memories\n\t\t    SET valid_until = ?, superseded_by = ?\n\t\t  WHERE id = ? AND valid_until IS NULL`,\n\t\tnow, newID, oldID,","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/mem/store.go#L212-L248","documentation":"Supersede failed while checking whether a memory with the replacement's ID already exists (SELECT COUNT(*) FROM memories WHERE id = ?). Any scan/query error other than a clean result is wrapped here and the transaction is abandoned so no partial lineage is written.","triggerScenarios":"Calling Store.Supersede(oldID, newText) when the COUNT query on the memories table fails: locked/corrupted database, disk I/O error, or schema mismatch.","commonSituations":"SQLite busy from concurrent writers; read-only or damaged database file; store opened with a different schema version than the one that created it.","solutions":["Inspect the wrapped cause with errors.Unwrap to identify lock vs I/O vs schema failure.","Set a busy_timeout pragma/DSN option and retry on SQLITE_BUSY.","Check disk space and file permissions on the store path.","Recreate or migrate the store file if the schema is outdated."],"exampleFix":"// before\nmem, err := store.Supersede(oldID, text)\nif err != nil { return err }\n// after\nmem, err := store.Supersede(oldID, text)\nif err != nil {\n\tvar serr *sqlite.Error\n\tif errors.As(err, &serr) && serr.Code() == sqlite.RESULT_BUSY {\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\tmem, err = store.Supersede(oldID, text)\n\t}\n\tif err != nil { return err }\n}","handlingStrategy":"retry","validationCode":"if _, err := os.Stat(storePath); err != nil { return err }","typeGuard":null,"tryCatchPattern":"for i := 0; i < 3; i++ {\n\tif _, err := store.Supersede(oldID, text); err == nil || !strings.Contains(err.Error(), \"locked\") {\n\t\treturn err\n\t}\n\ttime.Sleep(100 * time.Millisecond << i)\n}","preventionTips":["Enable busy_timeout to absorb SQLITE_BUSY","Limit concurrent writers to the store","Verify store file permissions and schema version before writing"],"tags":["database","sqlite","persistence"],"backgroundTag":"database-query-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"}