{"record":{"id":"9518c239e1259dee","repo":"gastownhall/beads","slug":"recompute-is-blocked-after-batch-delete-w","errorCode":null,"errorMessage":"recompute is_blocked after batch delete: %w","messagePattern":"recompute is_blocked after batch delete: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/delete.go","lineNumber":346,"sourceCode":"\t\t\tfmt.Sprintf(`DELETE FROM leases WHERE issue_id IN (%s)`, batchInClause),\n\t\t\tbatchArgs...); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"delete leases: %w\", err)\n\t\t}\n\t}\n\tresult.DeletedCount = totalRegularsDeleted + len(set.WispIDs)\n\n\t// Journal every regular issue this bulk/cascade delete removed. Wisps went\n\t// through deleteIssueRowInTx above, which journals each itself; set.All is\n\t// cascade-expanded, so this records cascade deletes too. The delete\n\t// plumbing carries no actor, so the rows record none.\n\tfor _, id := range journaledDeletes {\n\t\tif err := RecordDeleteInTx(ctx, tx, id, \"\"); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif err := RecomputeIsBlockedInTx(ctx, tx, affectedIssues, affectedWisps); err != nil {\n\t\treturn nil, fmt.Errorf(\"recompute is_blocked after batch delete: %w\", err)\n\t}\n\n\treturn result, nil\n}\n\n// ExistingIssueIDsInTableInTx returns the requested IDs that currently exist\n// in the selected issue table. It preserves caller ordering so delete and\n// journal records are deterministic across batches.\nfunc ExistingIssueIDsInTableInTx(ctx context.Context, tx DBTX, table string, ids []string) ([]string, error) {\n\tif len(ids) == 0 {\n\t\treturn nil, nil\n\t}\n\tswitch table {\n\tcase \"issues\", \"wisps\":\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported issue table %q\", table)\n\t}\n\texists := make(map[string]struct{}, len(ids))","sourceCodeStart":328,"sourceCodeEnd":364,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/delete.go#L328-L364","documentation":"After rows are deleted and journaled, DeleteResolvedSetInTx recomputes the is_blocked flag on issues that depended on the deleted ones. Failure of RecomputeIsBlockedInTx is wrapped with this message. The rows are already deleted in this transaction, so a failure here rolls everything back.","triggerScenarios":"DeleteIssuesInTx/DeleteInTx where the post-delete dependency recompute fails — corrupt or missing dependency rows, query error on dependencies/wisp_dependencies tables, or context cancellation during the recompute.","commonSituations":"Very large cascade deletes making the recompute slow enough to hit a context deadline; inconsistent dependency tables after a failed earlier sync.","solutions":["Read the wrapped inner error from RecomputeIsBlockedInTx for the root cause","Increase the context timeout for large cascades","Repair dependency-table consistency (bd doctor / integrity check) if the recompute queries fail on bad rows","Retry the delete; the transaction is atomic so no partial state remains"],"exampleFix":"// before\ndeleteCtx := ctx\n// after\ndeleteCtx, cancel := context.WithTimeout(ctx, 2*time.Minute)\ndefer cancel()\n// pass deleteCtx so RecomputeIsBlockedInTx is not cut short","handlingStrategy":"retry","validationCode":"// sanity-check dependency table integrity before a big cascade\nrows, _ := db.Query(\"SELECT COUNT(*) FROM dependencies WHERE issue_id IS NULL OR depends_on_id IS NULL\")\n// nonzero NULL count => repair before deleting","typeGuard":null,"tryCatchPattern":"res, err := DeleteIssuesInTx(ctx, tx, ids, opts)\nif err != nil && strings.Contains(err.Error(), \"recompute is_blocked\") {\n\t// whole tx rolled back; retry with longer deadline after repair\n\treturn retryWithTimeout(ctx, ids, 2*time.Minute)\n}\nreturn err","preventionTips":["Run integrity checks (bd doctor) to keep dependency rows consistent","Use generous context deadlines for cascade deletes","Keep the dependency tables free of NULL/corrupt rows"],"tags":["database","delete","is-blocked","transaction"],"backgroundTag":"bulk-delete-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}