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
- Inspect the wrapped inner error for the real cause (SQL syntax/constraint/connection)
- Retry the delete once the DB connection is healthy; the transaction rolls back atomically
- Increase the context timeout for large cascade deletes
- 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
- Use a context timeout proportional to delete-set size
- Keep the DB connection healthy (ping before long batch operations)
- Keep schema migrated so wisp deletes do not hit missing columns
- Avoid concurrent writers during bulk deletes
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
- delete: drop wisp deps: %w
- delete: drop wisp rows: %w
- hydrate neighbors (wisps): %w
- delete issues: %w
- delete leases: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/b37cf3a10885082b.
Report an issue: GitHub.