gastownhall/beads · error

recompute is_blocked after close for %s: %w

Error message

recompute is_blocked after close for %s: %w

What it means

This error wraps a failure of RecomputeIsBlockedInTxWithResult, which maintains derived is_blocked state on dependent issues after this issue closes. Since blocked-state is derived data, the library refuses to commit a close with a stale/incorrect recompute and rolls back the transaction. The issue ID and underlying error are included for diagnosis.

Source

Thrown at internal/storage/issueops/close.go:382

			return &CloseResult{IsWisp: isWisp, AlreadyClosed: true}, nil
		}
		return nil, fmt.Errorf("failed to close issue: %s", id)
	}

	// A closed issue holds no lease (no-op for wisps, which are never leased).
	if err := DeleteLeaseInTx(ctx, tx, id); err != nil {
		return nil, err
	}

	if recordEvent {
		if err := RecordEventInTable(ctx, tx, eventTable, id, types.EventClosed, actor, reason); err != nil {
			return nil, fmt.Errorf("failed to record event: %w", err)
		}
	}

	recompute, err := RecomputeIsBlockedInTxWithResult(ctx, tx, affectedIssues, affectedWisps)
	if err != nil {
		return nil, fmt.Errorf("recompute is_blocked after close for %s: %w", id, err)
	}

	// Snapshot only after all derived blocked-state maintenance has completed.
	// recordEvent gates the human audit event, never the journal.
	if err := RecordEventInTx(ctx, tx, EventClose, id, actor); err != nil {
		return nil, err
	}

	return &CloseResult{IsWisp: isWisp, IssueRowsChanged: !isWisp || recompute.IssueRowsChanged}, nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped error (errors.Is/As) — deadlocks/timeouts are the usual cause; retry with a fresh transaction.
  2. Increase the context timeout when closing issues with large dependency fan-out.
  3. Close issues in smaller batches to reduce transaction size and lock scope.
  4. Check schema/version consistency (bd doctor) and server health if failures persist.
Defensive patterns

Strategy: retry

Try / catch

err := store.CloseIssue(ctx, id, actor, reason)
if err != nil && strings.Contains(err.Error(), "recompute is_blocked after close") {
    if isDeadlockOrTimeout(err) {
        return retryClose(ctx, id, actor, reason, 1) // whole tx rolled back
    }
    return err
}

Prevention

When it happens

Trigger: RecomputeIsBlockedInTxWithResult returns an error while updating is_blocked for affectedIssues/affectedWisps inside the close transaction: large dependency graphs timing out, lock contention on dependent rows, missing columns, or connection failure mid-recompute.

Common situations: Closing a hub issue with hundreds of dependents in one transaction under a tight context deadline; deadlocks with other writers updating the same dependent rows; schema drift on the issues table; slow Dolt server under load.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/bb8e32f24ca6898f. Report an issue: GitHub.