{"record":{"id":"417a128dd8a27944","repo":"gastownhall/beads","slug":"affected-by-batched-wisp-delete-w","errorCode":null,"errorMessage":"affected by batched wisp delete: %w","messagePattern":"affected by batched wisp delete: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/wisps.go","lineNumber":436,"sourceCode":"\treturn totalDeleted, nil\n}\n\n// deleteWispBatchTx deletes one batch of wisps inside its own transaction.\n// Keeping each transaction to ≤200 wisps (6 DELETE statements) ensures it\n// completes well within Dolt's 10 s write timeout.\nfunc (s *DoltStore) deleteWispBatchTx(ctx context.Context, ids []string) (int, error) {\n\ttx, err := s.db.BeginTx(ctx, nil)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"failed to begin transaction: %w\", err)\n\t}\n\tdefer func() { _ = tx.Rollback() }()\n\n\tclearJournalScope := s.scopeEventsJournalTransaction(tx)\n\tdefer clearJournalScope()\n\n\taffectedIssues, affectedWisps, aerr := issueops.AffectedByDeletionInTx(ctx, tx, nil, ids)\n\tif aerr != nil {\n\t\treturn 0, fmt.Errorf(\"affected by batched wisp delete: %w\", aerr)\n\t}\n\n\t// Resolve WHICH wisps this batch actually removes before the DELETE runs:\n\t// afterwards they are gone, and RowsAffected reports a count, not a set.\n\t// GC hands this path ids it scanned earlier, so an already-collected wisp is\n\t// a routine case, and a phantom delete record would tell a consumer to drop\n\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)","sourceCodeStart":418,"sourceCodeEnd":454,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/wisps.go#L418-L454","documentation":"This error wraps failures from issueops.AffectedByDeletionInTx, the pre-delete step of the batched wisp delete transaction that computes which issues and wisps would be affected (e.g. via dependency edges) by removing the given wisp IDs. The batch transaction needs this set to recompute is_blocked afterward, so if the affected-set query fails, the whole batch rolls back rather than deleting wisps with stale block status. It is wrapped so the original SQL/transaction cause is preserved in the chain.","triggerScenarios":"Calling DeleteWisps (directly or via GC) when the SELECT behind AffectedByDeletionInTx fails: Dolt connection dropped or timed out, the transaction was cancelled via ctx, or the wisps/dependencies tables are missing or corrupted so the query errors.","commonSituations":"A GC run over a large ID set races with a Dolt server restart or network blip; running against a database where a migration didn't create the dependencies table; ctx cancellation when a caller's timeout expires mid-batch; SQLite-vs-Dolt schema drift in a partially-upgraded repository.","solutions":["Check Dolt server health/connectivity and retry the delete operation once the database is reachable","Verify the schema contains the tables referenced by AffectedByDeletionInTx (wisps, dependencies/issues); run schema migrations","Inspect the wrapped cause (%w) in logs for the underlying driver error and address it specifically (timeout, cancelled context, corrupt table)","If ctx timeouts are the cause, increase the caller's timeout or reduce batch pressure"],"exampleFix":"// before\nids := allScannedIDs // thousands of IDs, one long-running GC pass, one ctx timeout\nerr := store.DeleteWisps(ctx, ids)\n// after\nctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)\ndefer cancel()\nerr := store.DeleteWisps(ctx, ids) // batched internally at 200; healthy connection + complete schema\nif err != nil { log.Printf(\"batch wisp delete failed: %v\", err) }","handlingStrategy":"try-catch","validationCode":"// Go: check connectivity and context before the call\nif err := ctx.Err(); err != nil { return fmt.Errorf(\"context already cancelled: %w\", err) }\nif err := db.PingContext(ctx); err != nil { return fmt.Errorf(\"dolt unreachable: %w\", err) }","typeGuard":null,"tryCatchPattern":"deleted, err := store.DeleteWisps(ctx, ids)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {\n        // reschedule GC with a longer deadline\n    } else if strings.Contains(err.Error(), \"affected by batched wisp delete\") {\n        var driverErr *driverError // inspect wrapped cause via errors.As\n        log.Printf(\"affected-set lookup failed: %v\", err)\n    }\n    return err\n}","preventionTips":["Always pass a context with adequate timeout for GC-sized ID sets","Keep Dolt server and client versions in sync to avoid schema/query incompatibilities","Run schema migrations before enabling GC against an upgraded repo","Monitor Dolt connectivity before scheduled maintenance jobs"],"tags":["dolt","storage","transaction","gc"],"backgroundTag":"wisp-delete-affected-set-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}