{"record":{"id":"03ee72c9a37258c1","repo":"gastownhall/beads","slug":"expand-cascade-w","errorCode":null,"errorMessage":"expand cascade: %w","messagePattern":"expand cascade: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/delete.go","lineNumber":123,"sourceCode":"\t// read or a citation rewrite to, and what nothing else may recompute.\n\tAll []string\n}\n\n// ResolveDeletionSetInTx decides WHICH rows a delete removes: the named ids,\n// plus — under cascade — the transitive closure of everything that depends on\n// them, in BOTH planes.\n//\n// THE CASCADE IS ROOTED AT EVERY NAMED ID, WISPS INCLUDED. Rooting it at the\n// 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","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/delete.go#L105-L141","documentation":"ResolveDeletionSetInTx wraps a failure from FindAllDependentsInTx — the transitive-closure query that expands the requested ids into all dependents when cascade=true. Without this expansion, a cascading delete would orphan dependent issues, so the operation refuses to proceed.","triggerScenarios":"bd delete --cascade (or DeleteIssuesInTx/DeleteInTx with cascade=true) when the recursive dependents query fails: context timeout on a deep/large dependency graph, connection loss, or SQL error on the dependency planes.","commonSituations":"Cascading a delete across a large multi-layer dependency graph that exceeds the statement timeout; lock contention while another writer mutates dependencies; stale schema missing dependency tables.","solutions":["Inspect the wrapped cause for the concrete SQL error.","Increase the context timeout — cascade closure over big graphs is the slowest step of a delete.","Retry in a fresh transaction if it was a transient connection/lock failure.","Run without --cascade and delete in explicit batches to shrink the closure size."],"exampleFix":"// before\nbd delete bd-1 bd-2 --cascade // times out on a huge graph\n// after: larger timeout\nbd delete bd-1 bd-2 --cascade // with BEADS_DB_TIMEOUT=120s or a fresh context in code\ndelCtx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)\ndefer cancel()","handlingStrategy":"validation","validationCode":"// pre-check graph depth cheaply before a cascade\ndirect, err := externalDependentsOf(ctx, db, ids...)\nif err != nil { return err }\nif len(direct) > 1000 { /* use an extended timeout or delete in batches */ }","typeGuard":null,"tryCatchPattern":"res, err := storage.DeleteIssues(ctx, db, ids, true /*cascade*/, false, false)\nif err != nil {\n\tif errors.Is(err, context.DeadlineExceeded) {\n\t\t// retry with larger timeout or without cascade, in batches\n\t}\n\treturn err\n}","preventionTips":["Size the context timeout to graph depth when using --cascade.","Cascade in explicit batches for very large subgraphs.","Check connectivity/locks before wide cascading deletes."],"tags":["database","cascade","dependency-graph","error-wrapping"],"backgroundTag":"cascade-expansion-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}