gastownhall/beads · error

recompute is_blocked after source-repo delete: %w

Error message

recompute is_blocked after source-repo delete: %w

What it means

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.

Source

Thrown at internal/storage/issueops/bulk_ops.go:234

	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return 0, fmt.Errorf("rows affected: %w", err)
	}

	// Journal each deleted issue in the same transaction. issueIDs is the exact
	// set removed by the DELETE above (both were scoped to source_repo), so
	// there are no phantom records here. The source-repo bulk delete plumbing
	// carries no actor, so the rows record none.
	for _, id := range issueIDs {
		if err := RecordDeleteInTx(ctx, tx, id, ""); err != nil {
			return int(rowsAffected), err
		}
	}

	if err := RecomputeIsBlockedInTx(ctx, tx, affectedIssues, affectedWisps); err != nil {
		return int(rowsAffected), fmt.Errorf("recompute is_blocked after source-repo delete: %w", err)
	}

	return int(rowsAffected), nil
}

//nolint:gosec // G201: table names are hardcoded
func UpdateIssueIDInTx(ctx context.Context, tx *sql.Tx, oldID, newID string, issue *types.Issue, actor string) error {
	// Capture the edges under the OLD id before the rename rewrites them; they
	// are what the journal replays as a remove/re-add pair around the identity
	// change.
	var renameEdges []journalDependencyEdge
	if journalEnabled(ctx, tx) {
		var err error
		renameEdges, err = dependencyEdgesForIssueIDsInTx(ctx, tx, []string{oldID})
		if err != nil {
			return fmt.Errorf("capture dependency edges for rename %s -> %s: %w", oldID, newID, err)
		}
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the inner error from RecomputeIsBlockedInTx for the failing statement
  2. Retry the whole transaction; it is atomic so no partial delete persists
  3. Increase the context timeout for repos with wide dependency graphs
  4. Verify schema (is_blocked column and dependency tables) is current
Defensive patterns

Strategy: retry

Validate before calling

// pre-check is_blocked column and dependencies tables exist
_, err := db.Query("SELECT is_blocked FROM issues LIMIT 1")

Type guard

if errors.Is(err, context.DeadlineExceeded) { /* recompute timed out: retry with longer budget */ }

Try / catch

err := DeleteIssuesBySourceRepoInTx(ctx, tx, repo)
if err != nil && strings.Contains(err.Error(), "recompute is_blocked") {
    tx.Rollback() // avoid stale is_blocked state
    // retry entire transaction with a larger timeout
}

Prevention

When it happens

Trigger: 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.

Common situations: Large cross-repo dependency graphs making the recompute slow; schema drift on is_blocked; concurrent writers locking the tables during the update.

Related errors


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