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
- Inspect the wrapped cause and address the underlying SQL failure
- Chunk the wispIDs list into smaller batches instead of one giant IN clause
- Retry after transient DB/lock failures; the tx guarantees atomicity
- 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
- Chunk large IN-clause deletions (a few hundred IDs per call)
- Deduplicate wispIDs before building the clause
- Retry on transient DB errors; the tx keeps deletes atomic
- Ensure buildSQLInClause placeholder count always matches args
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
- ErrTransaction
- open unit of work: %w
- failed to begin transaction: %w
- failed to commit is_blocked repairs: %w
- failed to begin transaction: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/b1431d75c46fcba3.
Report an issue: GitHub.