{"record":{"id":"4f2a3f5e825e0cd0","repo":"gastownhall/beads","slug":"recompute-is-blocked-after-remove-dependency-s","errorCode":null,"errorMessage":"recompute is_blocked after remove dependency %s -> %s: %w","messagePattern":"recompute is_blocked after remove dependency (.+?) -> (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/dependencies.go","lineNumber":973,"sourceCode":"\t\t\tfmt.Sprintf(\"Removed dependency on %s\", dependsOnID)); err != nil {\n\t\t\treturn false, fmt.Errorf(\"record dependency_removed event: %w\", err)\n\t\t}\n\t\teventWritten = true\n\t}\n\n\tvar affectedIssues, affectedWisps []string\n\tvar aerr error\n\tif isWisp {\n\t\taffectedIssues, affectedWisps, aerr = AffectedByDepChangeForWispInTx(ctx, tx, issueID, dependsOnID, types.DependencyType(depType))\n\t} else {\n\t\taffectedIssues, affectedWisps, aerr = AffectedByDepChangeInTx(ctx, tx, issueID, dependsOnID, types.DependencyType(depType))\n\t}\n\tif aerr != nil {\n\t\treturn false, fmt.Errorf(\"affected by remove dependency %s -> %s: %w\", issueID, dependsOnID, aerr)\n\t}\n\trecomputed, err := RecomputeIsBlockedInTxWithResult(ctx, tx, affectedIssues, affectedWisps)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"recompute is_blocked after remove dependency %s -> %s: %w\", issueID, dependsOnID, err)\n\t}\n\tmergeRecomputeIsBlockedResult(recomputeResult, recomputed)\n\t// Snapshot only after all derived blocked-state maintenance has completed.\n\t// Never gated on emitEvent — a structural removal is as real to a replaying\n\t// consumer as one from an explicit dep verb.\n\treturn eventWritten, RecordDepEventInTx(ctx, tx, EventDepRemove, issueID, depType, dependsOnID, depMetadata, actor)\n}\n\nfunc mergeRecomputeIsBlockedResult(target *RecomputeIsBlockedResult, source RecomputeIsBlockedResult) {\n\tif target == nil {\n\t\treturn\n\t}\n\ttarget.IssueRowsChanged = target.IssueRowsChanged || source.IssueRowsChanged\n\ttarget.WispRowsChanged = target.WispRowsChanged || source.WispRowsChanged\n}\n\n// GetIssuesByIDsInTx retrieves multiple issues by ID within an existing\n// transaction, including labels. Automatically routes each ID to the correct","sourceCodeStart":955,"sourceCodeEnd":991,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependencies.go#L955-L991","documentation":"Wraps a failure from RecomputeIsBlockedInTxWithResult that occurs after a dependency removal inside a transaction. The library throws it so the caller knows the structural remove succeeded but the derived is_blocked maintenance pass failed; the whole transaction is rolled back with the dependency chain (issueID -> dependsOnID) in the message for diagnosis.","triggerScenarios":"removeDependencyInTx (via ApplyParentPatch or RemoveDependencyInTx) successfully deletes the dependency row and identifies affected issues/wisps, but RecomputeIsBlockedInTxWithResult returns an error while recomputing is_blocked for those affected issues.","commonSituations":"Database-level failures during the recompute (deadlock, lock wait timeout, connection drop mid-transaction), a corrupted dependency graph (missing rows the recompute expects), or driver errors scanning dependency rows for the affected set.","solutions":["Inspect the wrapped cause (%w) — it is almost always a lower-level DB error from the recompute query; fix that root cause first","Retry the operation; deadlocks and lock timeouts during recompute are often transient","Verify dependency table integrity (orphaned dependencies/wisp rows) if the error persists","Check DB connectivity and transaction timeout settings if errors cluster under load"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Before removing, confirm the dependency exists and the DB is reachable:\nif err := db.PingContext(ctx); err != nil {\n    return fmt.Errorf(\"db unreachable, skip dep removal: %w\", err)\n}\n// Optionally pre-check the dep row:\nvar n int\n_ = db.QueryRowContext(ctx, `SELECT COUNT(*) FROM dependencies WHERE issue_id = ? AND depends_on_id = ?`, issueID, dependsOnID).Scan(&n)\nif n == 0 { return nil } // nothing to remove, no recompute risk","typeGuard":null,"tryCatchPattern":"for attempt := 0; attempt < 3; attempt++ {\n    _, err := ops.RemoveDependencyInTx(ctx, tx, issueID, dependsOnID, depType)\n    if err == nil { break }\n    if !isTransientDBError(err) { return err } // errors contain \"recompute is_blocked after remove dependency\"\n    time.Sleep(backoff(attempt))\n}","preventionTips":["Keep transactions short to reduce deadlock/lock-timeout windows during recompute","Set retry-on-deadlock logic at the transaction layer","Monitor dependency-table integrity (orphan rows) with periodic checks","Keep schema migrations current to avoid recompute scan surprises"],"tags":["database","transaction","dependencies","go"],"backgroundTag":"dependency-recompute-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}