{"record":{"id":"5f83714caba5b062","repo":"gastownhall/beads","slug":"recompute-is-blocked-after-source-repo-delete-w","errorCode":null,"errorMessage":"recompute is_blocked after source-repo delete: %w","messagePattern":"recompute is_blocked after source-repo delete: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/bulk_ops.go","lineNumber":234,"sourceCode":"\t}\n\n\trowsAffected, err := result.RowsAffected()\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"rows affected: %w\", err)\n\t}\n\n\t// Journal each deleted issue in the same transaction. issueIDs is the exact\n\t// set removed by the DELETE above (both were scoped to source_repo), so\n\t// there are no phantom records here. The source-repo bulk delete plumbing\n\t// carries no actor, so the rows record none.\n\tfor _, id := range issueIDs {\n\t\tif err := RecordDeleteInTx(ctx, tx, id, \"\"); err != nil {\n\t\t\treturn int(rowsAffected), err\n\t\t}\n\t}\n\n\tif err := RecomputeIsBlockedInTx(ctx, tx, affectedIssues, affectedWisps); err != nil {\n\t\treturn int(rowsAffected), fmt.Errorf(\"recompute is_blocked after source-repo delete: %w\", err)\n\t}\n\n\treturn int(rowsAffected), nil\n}\n\n//nolint:gosec // G201: table names are hardcoded\nfunc UpdateIssueIDInTx(ctx context.Context, tx *sql.Tx, oldID, newID string, issue *types.Issue, actor string) error {\n\t// Capture the edges under the OLD id before the rename rewrites them; they\n\t// are what the journal replays as a remove/re-add pair around the identity\n\t// change.\n\tvar renameEdges []journalDependencyEdge\n\tif journalEnabled(ctx, tx) {\n\t\tvar err error\n\t\trenameEdges, err = dependencyEdgesForIssueIDsInTx(ctx, tx, []string{oldID})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"capture dependency edges for rename %s -> %s: %w\", oldID, newID, err)\n\t\t}\n\t}","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/bulk_ops.go#L216-L252","documentation":"After deletion and journaling, RecomputeIsBlockedInTx refreshes the is_blocked flag for affected issues/wisps; failure is wrapped as \"recompute is_blocked after source-repo delete: %w\". The delete and journal succeeded but the derived blocked-state update failed, so the transaction should roll back to avoid stale is_blocked values.","triggerScenarios":"RecomputeIsBlockedInTx errors: its UPDATE/SELECT over affected issues fails (missing columns, locked DB), the affected set is enormous and the context times out, or an earlier journal write poisoned the tx.","commonSituations":"Large cross-repo dependency graphs making the recompute slow; schema drift on is_blocked; concurrent writers locking the tables during the update.","solutions":["Inspect the inner error from RecomputeIsBlockedInTx for the failing statement","Retry the whole transaction; it is atomic so no partial delete persists","Increase the context timeout for repos with wide dependency graphs","Verify schema (is_blocked column and dependency tables) is current"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// pre-check is_blocked column and dependencies tables exist\n_, err := db.Query(\"SELECT is_blocked FROM issues LIMIT 1\")","typeGuard":"if errors.Is(err, context.DeadlineExceeded) { /* recompute timed out: retry with longer budget */ }","tryCatchPattern":"err := DeleteIssuesBySourceRepoInTx(ctx, tx, repo)\nif err != nil && strings.Contains(err.Error(), \"recompute is_blocked\") {\n    tx.Rollback() // avoid stale is_blocked state\n    // retry entire transaction with a larger timeout\n}","preventionTips":["Always roll back if the recompute fails — half-deleted state with stale flags is worse than no delete","Allow long contexts for repos with wide dependency graphs","Keep schema current so is_blocked and dependency tables match the recompute query"],"tags":["database","consistency","transaction"],"backgroundTag":"transaction-recompute-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}