gastownhall/beads · error

failed to batch delete wisps: %w

Error message

failed to batch delete wisps: %w

What it means

This error wraps a failure of the core DELETE FROM wisps WHERE id IN (...) statement inside the batch delete transaction. Everything before it (affected-set computation, existing-ID resolution, edge journaling) succeeded, but the actual row deletion failed, so the transaction rolls back with no wisps removed.

Source

Thrown at internal/storage/dolt/wisps.go:461

	// a bead this transaction never touched.
	deletedIDs, err := issueops.ExistingIssueIDsInTableInTx(ctx, tx, "wisps", ids)
	if err != nil {
		return 0, fmt.Errorf("resolve existing wisps for batch delete: %w", err)
	}
	// Edges are journaled before the rows go, while their source snapshots can
	// still be read.
	if err := issueops.RecordDependencyRemovalsForIssuesInTx(ctx, tx, deletedIDs); err != nil {
		return 0, fmt.Errorf("journal dependency removals for batched wisp delete: %w", err)
	}

	inClause, args := doltBuildSQLInClause(ids)

	//nolint:gosec // G201: inClause contains only ? markers
	result, err := tx.ExecContext(ctx,
		fmt.Sprintf("DELETE FROM wisps WHERE id IN (%s)", inClause),
		args...)
	if err != nil {
		return 0, fmt.Errorf("failed to batch delete wisps: %w", err)
	}
	rowsAffected, _ := result.RowsAffected()

	// The batched wisp delete surface carries no actor, so the rows record none.
	for _, id := range deletedIDs {
		if err := issueops.RecordDeleteInTx(ctx, tx, id, ""); err != nil {
			return 0, err
		}
	}

	if err := issueops.DeleteWispsFromDependenciesInTx(ctx, tx, ids); err != nil {
		return 0, err
	}

	if err := deleteWispAuxRowsInTx(ctx, tx, ids); err != nil {
		return 0, fmt.Errorf("delete wisp aux rows: %w", err)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry the delete; the wrapper batches at 200 IDs so transient failures are safe to re-attempt (nothing committed on failure)
  2. If calling internals directly, chunk ID lists to ≤200 to stay within Dolt's write timeout
  3. Check for concurrent writers/locks on the wisps table and serialize GC with other mutating jobs
  4. Verify the database is writable (not a read-only replica) and the connection is alive; check the wrapped driver error for specifics

Example fix

// before
store.deleteWispBatchTx(ctx, hugeIDSlice) // >200 ids: oversized IN clause, write timeout
// after
for i := 0; i < len(ids); i += 200 {
    end := i + 200
    if end > len(ids) { end = len(ids) }
    if _, err := store.deleteWispBatchTx(ctx, ids[i:end]); err != nil { return err }
}
Defensive patterns

Strategy: retry

Validate before calling

// Go: chunk large ID sets before calling delete internals
if len(ids) > 200 {
    return errors.New("split IDs into batches of <=200 before deleting")
}
if err := db.PingContext(ctx); err != nil { return fmt.Errorf("dolt unreachable: %w", err) }

Try / catch

n, err := store.DeleteWisps(ctx, ids)
if err != nil {
    if strings.Contains(err.Error(), "failed to batch delete wisps") {
        // transaction rolled back atomically; safe to retry
        time.Sleep(2 * time.Second)
        n, err = store.DeleteWisps(ctx, ids)
    }
    return err
}

Prevention

When it happens

Trigger: The parameterized DELETE errors: Dolt connection dropped mid-transaction, write timeout exceeded, table locked by a concurrent writer, or malformed/oversized IN clause (more IDs than the driver/server can handle).

Common situations: Deleting thousands of IDs in one statement against a remote Dolt server with a 10s write timeout; another process holding a row lock on wisps; server restarted between BeginTx and ExecContext; running against a read-only replica.

Related errors


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