gastownhall/beads · error

delete wisp %s: %w

Error message

delete wisp %s: %w

What it means

DeleteResolvedSetInTx wraps failures from deleteIssueRowInTx when removing each wisp row individually inside the delete transaction. Wisps are deleted one row at a time (rather than batched) because each wisp delete also journals itself. The wrapped error is whatever the underlying row-delete failed with (SQL error, context cancellation, etc.).

Source

Thrown at internal/storage/issueops/delete.go:304

	}

	// Resolve WHICH regular ids this delete actually removes before the batched
	// DELETE runs: afterwards the rows are gone, and RowsAffected reports a
	// count, not a set. A journal record for an id that was already absent would
	// tell a consumer to drop a bead this transaction never touched.
	journaledDeletes, err := journalableDeletesInTx(ctx, tx, "issues", set.RegularIDs)
	if err != nil {
		return nil, err
	}
	// Edges are journaled before the rows go, while their source snapshots can
	// still be read.
	if err := RecordDependencyRemovalsForIssuesInTx(ctx, tx, set.All); err != nil {
		return nil, fmt.Errorf("journal dependency removals for batch delete: %w", err)
	}

	for _, id := range set.WispIDs {
		if err := deleteIssueRowInTx(ctx, tx, id, true); err != nil {
			return nil, fmt.Errorf("delete wisp %s: %w", id, err)
		}
	}

	totalRegularsDeleted := 0
	for i := 0; i < len(set.RegularIDs); i += deleteBatchSize {
		end := i + deleteBatchSize
		if end > len(set.RegularIDs) {
			end = len(set.RegularIDs)
		}
		batch := set.RegularIDs[i:end]
		batchInClause, batchArgs := buildSQLInClause(batch)

		deleteResult, err := tx.ExecContext(ctx,
			fmt.Sprintf(`DELETE FROM issues WHERE id IN (%s)`, batchInClause),
			batchArgs...)
		if err != nil {
			return nil, fmt.Errorf("delete issues: %w", err)
		}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped inner error for the real cause (SQL syntax/constraint/connection)
  2. Retry the delete once the DB connection is healthy; the transaction rolls back atomically
  3. Increase the context timeout for large cascade deletes
  4. Verify schema is current (run bd's migration/doctor) if the inner error indicates a missing column/table

Example fix

// before
if err := deleteIssueRowInTx(ctx, tx, id, true); err != nil {
	return nil, fmt.Errorf("delete wisp %s: %w", id, err)
}
// after — same code; fix the cause by giving the operation a generous deadline
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
Defensive patterns

Strategy: retry

Validate before calling

// check DB reachability and schema before deleting
if err := db.PingContext(ctx); err != nil { return fmt.Errorf("db unreachable: %w", err) }

Try / catch

if _, err := DeleteIssuesInTx(ctx, tx, ids, opts); err != nil {
	var wrappedErr error
	if strings.Contains(err.Error(), "delete wisp ") {
		// extract wisp id and inner cause; retry with fresh tx
		wrappedErr = retryDelete(ctx, ids)
	}
	return wrappedErr
}

Prevention

When it happens

Trigger: Calling DeleteIssuesInTx or DeleteInTx (e.g. `bd delete --cascade` or bulk delete) where the resolved deletion set includes wisp IDs and the per-wisp DELETE fails — DB constraint violation, connection drop, or context timeout mid-loop.

Common situations: Deleting a large resolved set against a Dolt/SQLite database when the connection drops mid-transaction; running deletes with a short context deadline; schema drift where the wisps table lacks an expected column.

Related errors


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