{"record":{"id":"5bb601e013c72774","repo":"gastownhall/beads","slug":"failed-to-batch-delete-wisps-w-5bb601","errorCode":null,"errorMessage":"failed to batch delete wisps: %w","messagePattern":"failed to batch delete wisps: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/wisps.go","lineNumber":461,"sourceCode":"\t// a bead this transaction never touched.\n\tdeletedIDs, err := issueops.ExistingIssueIDsInTableInTx(ctx, tx, \"wisps\", ids)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"resolve existing wisps for batch delete: %w\", err)\n\t}\n\t// Edges are journaled before the rows go, while their source snapshots can\n\t// still be read.\n\tif err := issueops.RecordDependencyRemovalsForIssuesInTx(ctx, tx, deletedIDs); err != nil {\n\t\treturn 0, fmt.Errorf(\"journal dependency removals for batched wisp delete: %w\", err)\n\t}\n\n\tinClause, args := doltBuildSQLInClause(ids)\n\n\t//nolint:gosec // G201: inClause contains only ? markers\n\tresult, err := tx.ExecContext(ctx,\n\t\tfmt.Sprintf(\"DELETE FROM wisps WHERE id IN (%s)\", inClause),\n\t\targs...)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"failed to batch delete wisps: %w\", err)\n\t}\n\trowsAffected, _ := result.RowsAffected()\n\n\t// The batched wisp delete surface carries no actor, so the rows record none.\n\tfor _, id := range deletedIDs {\n\t\tif err := issueops.RecordDeleteInTx(ctx, tx, id, \"\"); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\n\tif err := issueops.DeleteWispsFromDependenciesInTx(ctx, tx, ids); err != nil {\n\t\treturn 0, err\n\t}\n\n\tif err := deleteWispAuxRowsInTx(ctx, tx, ids); err != nil {\n\t\treturn 0, fmt.Errorf(\"delete wisp aux rows: %w\", err)\n\t}\n","sourceCodeStart":443,"sourceCodeEnd":479,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/wisps.go#L443-L479","documentation":"This error wraps a failure of the core DELETE FROM wisps WHERE id IN (...) statement inside the batch delete transaction. Everything before it (affected-set computation, existing-ID resolution, edge journaling) succeeded, but the actual row deletion failed, so the transaction rolls back with no wisps removed.","triggerScenarios":"The parameterized DELETE errors: Dolt connection dropped mid-transaction, write timeout exceeded, table locked by a concurrent writer, or malformed/oversized IN clause (more IDs than the driver/server can handle).","commonSituations":"Deleting thousands of IDs in one statement against a remote Dolt server with a 10s write timeout; another process holding a row lock on wisps; server restarted between BeginTx and ExecContext; running against a read-only replica.","solutions":["Retry the delete; the wrapper batches at 200 IDs so transient failures are safe to re-attempt (nothing committed on failure)","If calling internals directly, chunk ID lists to ≤200 to stay within Dolt's write timeout","Check for concurrent writers/locks on the wisps table and serialize GC with other mutating jobs","Verify the database is writable (not a read-only replica) and the connection is alive; check the wrapped driver error for specifics"],"exampleFix":"// before\nstore.deleteWispBatchTx(ctx, hugeIDSlice) // >200 ids: oversized IN clause, write timeout\n// after\nfor i := 0; i < len(ids); i += 200 {\n    end := i + 200\n    if end > len(ids) { end = len(ids) }\n    if _, err := store.deleteWispBatchTx(ctx, ids[i:end]); err != nil { return err }\n}","handlingStrategy":"retry","validationCode":"// Go: chunk large ID sets before calling delete internals\nif len(ids) > 200 {\n    return errors.New(\"split IDs into batches of <=200 before deleting\")\n}\nif err := db.PingContext(ctx); err != nil { return fmt.Errorf(\"dolt unreachable: %w\", err) }","typeGuard":null,"tryCatchPattern":"n, err := store.DeleteWisps(ctx, ids)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to batch delete wisps\") {\n        // transaction rolled back atomically; safe to retry\n        time.Sleep(2 * time.Second)\n        n, err = store.DeleteWisps(ctx, ids)\n    }\n    return err\n}","preventionTips":["Rely on the library's internal 200-id batching; never build oversized IN clauses yourself","Serialize GC with other writers to avoid row-lock contention on wisps","Verify the database is writable (not a read-only replica) before maintenance","Retry on transient failures — the per-batch transaction guarantees atomicity"],"tags":["dolt","sql","delete","transaction"],"backgroundTag":"sql-delete-statement-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}