{"record":{"id":"0ddd7d843ca45944","repo":"gastownhall/beads","slug":"partition-delete-ids-w","errorCode":null,"errorMessage":"partition delete ids: %w","messagePattern":"partition delete ids: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/delete.go","lineNumber":132,"sourceCode":"// durable half is what made `bd wisp gc` (which hardcodes cascade) silently\n// under-delete. It does not read the caller's slice destructively either: the\n// non-cascade set is a copy, because DeleteRequest promises IDs is never\n// sorted in place.\nfunc ResolveDeletionSetInTx(ctx context.Context, tx DBTX, ids []string, cascade bool) (DeletionSet, error) {\n\tall := append([]string(nil), ids...)\n\tif cascade {\n\t\tclosure, err := FindAllDependentsInTx(ctx, tx, ids)\n\t\tif err != nil {\n\t\t\treturn DeletionSet{}, fmt.Errorf(\"expand cascade: %w\", err)\n\t\t}\n\t\tall = workapi.SortedDeleteIDs(closure)\n\t}\n\tif len(all) == 0 {\n\t\treturn DeletionSet{}, nil\n\t}\n\twispIDs, regularIDs, err := PartitionWispIDsInTx(ctx, tx, all)\n\tif err != nil {\n\t\treturn DeletionSet{}, fmt.Errorf(\"partition delete ids: %w\", err)\n\t}\n\treturn DeletionSet{WispIDs: wispIDs, RegularIDs: regularIDs, All: all}, nil\n}\n\nfunc DeleteIssuesInTx(ctx context.Context, tx *sql.Tx, ids []string, cascade bool, force bool, dryRun bool) (*types.DeleteIssuesResult, error) {\n\tif len(ids) == 0 {\n\t\treturn &types.DeleteIssuesResult{}, nil\n\t}\n\n\tset, err := ResolveDeletionSetInTx(ctx, tx, ids, cascade)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar orphaned []string\n\tif !cascade {\n\t\t// The guard here is the STORAGE SEAM's, and it stays durable-only: the\n\t\t// server-backed store peels wisps off before it ever reaches this","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/delete.go#L114-L150","documentation":"ResolveDeletionSetInTx wraps a failure from PartitionWispIDsInTx, which sorts the deletion set's ids into wisp rows vs regular issue rows. Every subsequent step depends on this routing, so a failure here aborts the delete. This is an internal lookup (SELECT id FROM each routed table), typically a driver/connection issue.","triggerScenarios":"Any delete (DeleteIssuesInTx/DeleteInTx, including cascade flows) when the wisp-partition query fails after the id list is built: context canceled, connection dropped, or SQL error on the routed tables.","commonSituations":"Long-running bulk deletes whose context expired before partitioning; database lock/availability problems; rare schema mismatch where a routed table is absent.","solutions":["Check the wrapped driver error for the root cause.","Retry the delete with a fresh transaction and sufficient context lifetime — bulk deletes are long; size the timeout to the id count.","Run schema migration if a routed table is reported missing.","Split very large bulk deletes into smaller batches."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if err := ctx.Err(); err != nil { return fmt.Errorf(\"context expired before delete: %w\", err) }","typeGuard":null,"tryCatchPattern":"if err != nil {\n\tif isTransientDBError(err) { /* retry bulk delete in a fresh tx */ }\n\treturn err\n}","preventionTips":["Budget context time proportional to the number of ids in a bulk delete.","Split large bulk deletes into smaller batches.","Run migrations after upgrades so routed tables exist."],"tags":["database","transaction","partitioning","error-wrapping"],"backgroundTag":"transaction-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}