gastownhall/beads · error
recompute is_blocked after remove dependency %s -> %s: %w
Error message
recompute is_blocked after remove dependency %s -> %s: %w
What it means
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.
Source
Thrown at internal/storage/issueops/dependencies.go:973
fmt.Sprintf("Removed dependency on %s", dependsOnID)); err != nil {
return false, fmt.Errorf("record dependency_removed event: %w", err)
}
eventWritten = true
}
var affectedIssues, affectedWisps []string
var aerr error
if isWisp {
affectedIssues, affectedWisps, aerr = AffectedByDepChangeForWispInTx(ctx, tx, issueID, dependsOnID, types.DependencyType(depType))
} else {
affectedIssues, affectedWisps, aerr = AffectedByDepChangeInTx(ctx, tx, issueID, dependsOnID, types.DependencyType(depType))
}
if aerr != nil {
return false, fmt.Errorf("affected by remove dependency %s -> %s: %w", issueID, dependsOnID, aerr)
}
recomputed, err := RecomputeIsBlockedInTxWithResult(ctx, tx, affectedIssues, affectedWisps)
if err != nil {
return false, fmt.Errorf("recompute is_blocked after remove dependency %s -> %s: %w", issueID, dependsOnID, err)
}
mergeRecomputeIsBlockedResult(recomputeResult, recomputed)
// Snapshot only after all derived blocked-state maintenance has completed.
// Never gated on emitEvent — a structural removal is as real to a replaying
// consumer as one from an explicit dep verb.
return eventWritten, RecordDepEventInTx(ctx, tx, EventDepRemove, issueID, depType, dependsOnID, depMetadata, actor)
}
func mergeRecomputeIsBlockedResult(target *RecomputeIsBlockedResult, source RecomputeIsBlockedResult) {
if target == nil {
return
}
target.IssueRowsChanged = target.IssueRowsChanged || source.IssueRowsChanged
target.WispRowsChanged = target.WispRowsChanged || source.WispRowsChanged
}
// GetIssuesByIDsInTx retrieves multiple issues by ID within an existing
// transaction, including labels. Automatically routes each ID to the correctView on GitHub (pinned to 71377f2769)
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
Defensive patterns
Strategy: retry
Validate before calling
// Before removing, confirm the dependency exists and the DB is reachable:
if err := db.PingContext(ctx); err != nil {
return fmt.Errorf("db unreachable, skip dep removal: %w", err)
}
// Optionally pre-check the dep row:
var n int
_ = db.QueryRowContext(ctx, `SELECT COUNT(*) FROM dependencies WHERE issue_id = ? AND depends_on_id = ?`, issueID, dependsOnID).Scan(&n)
if n == 0 { return nil } // nothing to remove, no recompute risk Try / catch
for attempt := 0; attempt < 3; attempt++ {
_, err := ops.RemoveDependencyInTx(ctx, tx, issueID, dependsOnID, depType)
if err == nil { break }
if !isTransientDBError(err) { return err } // errors contain "recompute is_blocked after remove dependency"
time.Sleep(backoff(attempt))
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ErrTransaction
- open unit of work: %w
- failed to begin transaction: %w
- failed to commit is_blocked repairs: %w
- failed to begin transaction: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/4f2a3f5e825e0cd0.
Report an issue: GitHub.