{"record":{"id":"e3a276c981de579e","repo":"gastownhall/beads","slug":"recompute-is-blocked-after-delete-for-s-w","errorCode":null,"errorMessage":"recompute is_blocked after delete for %s: %w","messagePattern":"recompute is_blocked after delete for (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/delete.go","lineNumber":47,"sourceCode":"\t} else {\n\t\tdeletedIssues = []string{id}\n\t}\n\taffectedIssues, affectedWisps, aerr := AffectedByDeletionInTx(ctx, tx, deletedIssues, deletedWisps)\n\tif aerr != nil {\n\t\treturn fmt.Errorf(\"affected by delete for %s: %w\", id, aerr)\n\t}\n\n\t// Edges are journaled before the rows go, while their source snapshots can\n\t// still be read.\n\tif err := RecordDependencyRemovalsForIssuesInTx(ctx, tx, []string{id}); err != nil {\n\t\treturn fmt.Errorf(\"journal dependency removals for %s: %w\", id, err)\n\t}\n\tif err := deleteIssueRowInTx(ctx, tx, id, isWisp); err != nil {\n\t\treturn err\n\t}\n\n\tif err := RecomputeIsBlockedInTx(ctx, tx, affectedIssues, affectedWisps); err != nil {\n\t\treturn fmt.Errorf(\"recompute is_blocked after delete for %s: %w\", id, err)\n\t}\n\n\treturn nil\n}\n\n//nolint:gosec // G201: table names come from WispTableRouting (hardcoded constants)\nfunc deleteIssueRowInTx(ctx context.Context, tx *sql.Tx, id string, isWisp bool) error {\n\tissueTable, _, _, _ := WispTableRouting(isWisp)\n\tresult, err := tx.ExecContext(ctx, fmt.Sprintf(\"DELETE FROM %s WHERE id = ?\", issueTable), id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"delete issue from %s: %w\", issueTable, err)\n\t}\n\trows, err := result.RowsAffected()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"get rows affected: %w\", err)\n\t}\n\tif rows == 0 {\n\t\t// Wrap the sentinel so callers can errors.Is(..., storage.ErrNotFound),","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/delete.go#L29-L65","documentation":"DeleteIssueInTx wraps a failure from RecomputeIsBlockedInTx, the step that recalculates the is_blocked flag for every issue/wisp affected by the deletion. The issue row itself was already deleted at this point, but everything is in one transaction, so the whole delete rolls back and the issue still exists.","triggerScenarios":"Calling DeleteIssue/DeleteIssueInTx when the is_blocked recompute UPDATE fails — SQL error on the affected-issues set, context canceled, or lost connection after the row delete.","commonSituations":"Very large affected-issues set (deleting a hub issue with many dependents) causing a slow recompute that exceeds the statement/context timeout; concurrent writers contending on rows being recomputed.","solutions":["Retry the delete in a fresh transaction — rollback guarantees the issue was not actually removed.","Increase the context timeout when deleting issues with large dependent graphs.","Check the wrapped error for lock-wait-timeout and reduce concurrent writes to the same subgraph.","Verify the schema is current (stale schema can make the recompute statement fail)."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// estimate affected subgraph before deleting\ndeps, err := externalDependentsOf(ctx, db, id)\nif err == nil && len(deps) > 500 { /* use a longer context for the delete */ }","typeGuard":null,"tryCatchPattern":"if err := storage.DeleteIssue(ctx, db, id); err != nil {\n\tif errors.Is(err, context.DeadlineExceeded) || isLockTimeout(err) {\n\t\t// issue still exists (tx rolled back); retry with bigger timeout\n\t}\n\treturn err\n}","preventionTips":["Delete hub issues (many dependents) with long timeouts or during low-write windows.","Remember rollback semantics: a failure here leaves the issue intact.","Reduce concurrent writers to the same dependency subgraph."],"tags":["database","transaction","error-wrapping","is-blocked"],"backgroundTag":"transaction-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}