{"record":{"id":"3779e0172f5176f0","repo":"gastownhall/beads","slug":"delete-recompute-is-blocked-w","errorCode":null,"errorMessage":"delete: recompute is_blocked: %w","messagePattern":"delete: recompute is_blocked: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/issue_delete.go","lineNumber":215,"sourceCode":"\tif err != nil {\n\t\treturn result, fmt.Errorf(\"delete: drop issue rows: %w\", err)\n\t}\n\twispsDeleted, err := u.issueRepo.DeleteByIDs(ctx, wispIDs, IssueTableOpts{UseWispsTable: true})\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"delete: drop wisp rows: %w\", err)\n\t}\n\tresult.DeletedCount = issuesDeleted + wispsDeleted\n\n\tif params.UpdateTextReferences && len(connected) > 0 {\n\t\trefs, err := u.rewriteTextReferences(ctx, allIDs, connected, connectedIsWisp, actor)\n\t\tif err != nil {\n\t\t\treturn result, fmt.Errorf(\"delete: rewrite text references: %w\", err)\n\t\t}\n\t\tresult.ReferencesUpdated = refs\n\t}\n\n\tif err := u.issueRepo.RecomputeIsBlocked(ctx, affectedIssues, affectedWisps); err != nil {\n\t\treturn result, fmt.Errorf(\"delete: recompute is_blocked: %w\", err)\n\t}\n\n\treturn result, nil\n}\n\n// externalDependents finds the direct dependents of each id in ids that are\n// not themselves in ids, across both the issue and wisp dependency tables.\n// The result maps deletion-set id -> external dependent ids (unsorted).\nfunc (u *issueUseCaseImpl) externalDependents(ctx context.Context, ids []string) (map[string][]string, error) {\n\tidSet := make(map[string]bool, len(ids))\n\tfor _, id := range ids {\n\t\tidSet[id] = true\n\t}\n\n\tissueRes, err := u.depRepo.ListByIssueIDs(ctx, ids, DepListOpts{Direction: DepDirectionIn})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"delete: list dependents: %w\", err)\n\t}","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/issue_delete.go#L197-L233","documentation":"This error wraps a failure from issueRepo.RecomputeIsBlocked during deleteMany, the final step of deleting issues/wisps. After the dependency, label, event, and issue rows have already been removed, the use case recomputes the is_blocked flag on issues that were affected by the deletion (their blockers changed). The delete itself has already mutated the database, so a failure here leaves the delete partially applied with stale blocked-state flags.","triggerScenarios":"Calling DeleteIssue, DeleteWisp, DeleteIssues, or DeleteWisps when the underlying RecomputeIsBlocked query fails — e.g. DB connection dropped mid-transaction, lock contention on affected rows, or a storage-driver error scanning affected issues/wisps.","commonSituations":"Deleting a large batch whose dependency graph touches many issues, causing long-running recompute queries that hit statement timeouts; concurrent writers holding locks on dependent issues; embedded Dolt/driver connection failures during the final phase of a delete.","solutions":["Inspect the wrapped cause (%w) to identify the driver-level failure and fix that first (connection, timeout, lock).","Retry the delete path: dependency rows are deleted idempotently by ID, so re-running after a transient failure is safe.","Check for concurrent processes modifying the same issues and serialize deletes against them.","If blocked flags are stale but the delete succeeded, run a manual recompute/consistency pass instead of deleting again."],"exampleFix":"// before\nif err := u.issueRepo.RecomputeIsBlocked(ctx, affectedIssues, affectedWisps); err != nil {\n    return result, fmt.Errorf(\"delete: recompute is_blocked: %w\", err)\n}\n// after\nif err := u.issueRepo.RecomputeIsBlocked(ctx, affectedIssues, affectedWisps); err != nil {\n    return result, fmt.Errorf(\"delete: recompute is_blocked (deleted=%d, affectedIssues=%d): %w\", result.DeletedCount, len(affectedIssues), err)\n}","handlingStrategy":"try-catch","validationCode":"// Go has no pre-call check; verify DB reachability and schema first:\nif err := store.PingContext(ctx); err != nil {\n    return fmt.Errorf(\"database unavailable before delete: %w\", err)\n}","typeGuard":"// Narrow the wrapped cause with errors.As:\nvar dbErr *dberrors.DBError\nif errors.As(err, &dbErr) {\n    // handle driver-level failure specifically\n}","tryCatchPattern":"res, err := uc.DeleteIssues(ctx, ids, opts)\nif err != nil {\n    if strings.Contains(err.Error(), \"delete: recompute is_blocked\") {\n        // delete partially applied; inspect wrapped cause, retry or run recompute\n        return fmt.Errorf(\"delete applied but blocked-state recompute failed: %w\", err)\n    }\n    return err\n}","preventionTips":["Keep delete batches small to limit recompute scope","Avoid concurrent writers on the same issues during deletes","Set generous statement timeouts for dependency-heavy deletes","Always log the fully wrapped error chain with %w/%+v"],"tags":["go","storage","delete","is-blocked"],"backgroundTag":"delete-recompute-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}