{"record":{"id":"14475a58d6f58910","repo":"gastownhall/beads","slug":"get-dependents-w","errorCode":null,"errorMessage":"get dependents: %w","messagePattern":"get dependents: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/delete.go","lineNumber":163,"sourceCode":"\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\n\t\t// function (dolt/issues.go DeleteIssues), so widening it would make the\n\t\t// embedded store refuse where the server-backed one cannot. The ROLE's\n\t\t// guard, which does cover both planes, is in DeleteInTx.\n\t\tidSet := make(map[string]bool, len(ids))\n\t\tfor _, id := range ids {\n\t\t\tidSet[id] = true\n\t\t}\n\t\t// One scan of the dependency planes answers both modes: the guard needs\n\t\t// to know WHICH id is blocked, and the forced path needs the union of\n\t\t// what it orphans.\n\t\texternal, err := ExternalDependentsBySourceInTx(ctx, tx, set.RegularIDs, idSet)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"get dependents: %w\", err)\n\t\t}\n\t\tif !force {\n\t\t\tfor _, id := range set.RegularIDs {\n\t\t\t\tif deps := external[id]; len(deps) > 0 {\n\t\t\t\t\treturn &types.DeleteIssuesResult{OrphanedIssues: deps},\n\t\t\t\t\t\t&publicops.DependentsOutsideRequestError{IssueID: id, Dependents: deps}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\torphans := make(map[string]bool)\n\t\t\tfor _, deps := range external {\n\t\t\t\tfor _, id := range deps {\n\t\t\t\t\torphans[id] = true\n\t\t\t\t}\n\t\t\t}\n\t\t\torphaned = workapi.SortedDeleteIDs(orphans)\n\t\t}\n\t}","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/delete.go#L145-L181","documentation":"DeleteIssuesInTx wraps a failure from ExternalDependentsBySourceInTx — the single dependency-plane scan used both to guard non-forced deletes and to compute orphans for forced ones. The guard returns a DependentsOutsideRequestError when a regular issue would be orphaned; this wrap only fires when that scan itself fails.","triggerScenarios":"Bulk delete (bd delete with multiple ids, also invoked from SweepInTx) when the external-dependents query errors: context timeout scanning many regular ids, connection loss, or SQL failure on the dependency tables.","commonSituations":"Deleting a large batch where the dependents scan times out; concurrent dependency writes causing lock waits; degraded Dolt server under memory pressure during the join scan.","solutions":["Read the wrapped SQL error for the concrete cause.","Retry the bulk delete in a fresh transaction; batch the ids to shrink the scan.","Increase the context timeout proportional to batch size.","If lock-wait appears, pause concurrent writers (compact, sweep) during the delete."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// keep batches small enough that the dependents scan stays fast\nif len(ids) > 500 { ids = ids[:500] /* process in chunks */ }","typeGuard":null,"tryCatchPattern":"res, err := storage.DeleteIssues(ctx, db, ids, cascade, force, dryRun)\nif err != nil {\n\tif isTransientDBError(err) { /* fresh tx retry */ }\n\treturn err\n}\n// note: res.OrphanedIssues + DependentsOutsideRequestError are the expected\n// guard outcome, distinct from this wrap.","preventionTips":["Batch bulk deletes; the external-dependents scan scales with id count.","Use dryRun first to see the intended deletion set without the guard scan failing late.","Avoid concurrent sweeps/deletes on the same ids."],"tags":["database","bulk-delete","dependents","error-wrapping"],"backgroundTag":"transaction-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}