gastownhall/beads · error

delete wisps from dependencies: %w

Error message

delete wisps from dependencies: %w

What it means

Batch variant of the wisp cleanup: wraps a failed DELETE ... WHERE depends_on_wisp_id IN (...) executed by DeleteWispsFromDependenciesInTx. Failing here aborts the surrounding transaction so bulk deletions never leave half-removed dependency edges.

Source

Thrown at internal/storage/issueops/dependencies.go:595

func DeleteWispFromDependenciesInTx(ctx context.Context, tx *sql.Tx, wispID string) error {
	if _, err := tx.ExecContext(ctx,
		"DELETE FROM dependencies WHERE depends_on_wisp_id = ?", wispID); err != nil {
		return fmt.Errorf("delete wisp %s from dependencies: %w", wispID, err)
	}
	return nil
}

//nolint:gosec // G201: inClause contains only ? placeholders
func DeleteWispsFromDependenciesInTx(ctx context.Context, tx *sql.Tx, wispIDs []string) error {
	if len(wispIDs) == 0 {
		return nil
	}
	inClause, args := buildSQLInClause(wispIDs)
	if _, err := tx.ExecContext(ctx,
		fmt.Sprintf("DELETE FROM dependencies WHERE depends_on_wisp_id IN (%s)", inClause),
		args...); err != nil {
		return fmt.Errorf("delete wisps from dependencies: %w", err)
	}
	return nil
}

// Dependency target rewrites reinsert matching rows because Dolt can leave the
// stored generated depends_on_id column stale after a split target column is
// updated by FK cascade.
func UpdateWispIDInDependenciesInTx(ctx context.Context, tx *sql.Tx, oldID, newID string) error {
	for _, table := range []string{"dependencies", "wisp_dependencies"} {
		if err := replaceDependencyTargetInTx(ctx, tx, table, "depends_on_wisp_id", oldID, newID); err != nil {
			return fmt.Errorf("update wisp %s -> %s in %s: %w", oldID, newID, table, err)
		}
	}
	return nil
}

func UpdateIssueIDInDependenciesInTx(ctx context.Context, tx *sql.Tx, oldID, newID string) error {
	for _, table := range []string{"dependencies", "wisp_dependencies"} {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped cause and address the underlying SQL failure
  2. Chunk the wispIDs list into smaller batches instead of one giant IN clause
  3. Retry after transient DB/lock failures; the tx guarantees atomicity
  4. Verify buildSQLInClause output has the right placeholder count for the args

Example fix

// before
if err := issueops.DeleteWispsFromDependenciesInTx(ctx, tx, wispIDs); err != nil { return err }
// after: chunk large ID lists
for chunk := range slices.Chunk(wispIDs, 500) {
	if err := issueops.DeleteWispsFromDependenciesInTx(ctx, tx, chunk); err != nil {
		return fmt.Errorf("delete wisp deps chunk: %w", err)
	}
}
Defensive patterns

Strategy: validation

Validate before calling

// validate ID list and chunk size before bulk delete
if len(wispIDs) == 0 { return nil }
if len(wispIDs) > 1000 {
	return errors.New("chunk wispIDs into batches of <=1000")
}

Type guard

func isBulkWispCleanupFailure(err error) bool {
	return err != nil && strings.Contains(err.Error(), "delete wisps from dependencies")
}

Try / catch

err := issueops.DeleteWispsFromDependenciesInTx(ctx, tx, ids)
if err != nil {
	if isRetryableDriverErr(err) {
		return retryWithBackoff(op)
	}
	return err
}

Prevention

When it happens

Trigger: Calling DeleteWispsFromDependenciesInTx with a list of wisp IDs where the bulk DELETE errors — connection loss, SQL statement size/limit issues with a very large IN clause, or lock contention on the dependencies table.

Common situations: Bulk issue/wisp deletion during migrations or sync; oversized ID lists producing huge SQL statements; Dolt write conflicts with concurrent dependency mutations.

Related errors


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