gastownhall/beads · error
delete: %w
Error message
delete: %w
What it means
This wraps a failure from ResolveDeletionSetInTx, which computes the single deletion set (the cascade closure when Cascade=true) shared by the neighborhood read, the delete and the citation rewrite. The source notes the set is resolved exactly once because resolving it twice drifted — wisp-rooted cascade rows survived while their neighbors' text claimed they were deleted. A failure here aborts the whole delete before any write, so nothing is partially applied.
Source
Thrown at internal/storage/issueops/delete_role.go:140
} else {
orphaned := make(map[string]bool)
for _, deps := range external {
for _, id := range deps {
orphaned[id] = true
}
}
result.Orphaned = workapi.SortedDeleteIDs(orphaned)
}
}
// THE DELETION SET IS RESOLVED ONCE, HERE, and the same value reaches the
// neighborhood read, the deletion and the citation rewrite. Resolving it
// twice drifts: a cascade rooted at a wisp landed in the rewrite set and not
// in the delete, so those rows survived and their neighbors' descriptions
// were rewritten to call them deleted. See DeletionSet.
set, err := ResolveDeletionSetInTx(ctx, tx, ids, req.Cascade)
if err != nil {
return publicops.DeleteResult{}, fmt.Errorf("delete: %w", err)
}
// The neighborhood is read BEFORE the deletion, because after it the
// edges that identify a neighbor are gone. It is read against the whole
// deletion set — the cascade closure, not just the named ids — so a row
// citing a cascade-deleted id is rewritten too.
neighbors, err := deleteNeighborsInTx(ctx, tx, set.All)
if err != nil {
return publicops.DeleteResult{}, err
}
// No guard argument to pass: this body has ALREADY answered the guard
// question above, and DeleteResolvedSetInTx deletes what it is handed.
deleted, err := DeleteResolvedSetInTx(ctx, tx, set, req.DryRun)
if err != nil {
return publicops.DeleteResult{}, err
}
result.Deleted = deleted.DeletedCountView on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped error from ResolveDeletionSetInTx for the real cause
- Retry with a fresh transaction; nothing was written, so retry is safe
- For deep cascades, raise the context timeout or delete the graph in stages
- Verify dependency-table integrity (orphaned/looping edges) if failures recur on the same ids
- Check migrations are current on the backend
Defensive patterns
Strategy: retry
Validate before calling
// nothing to validate client-side; ensure ctx budget fits the cascade size
if err := ctx.Err(); err != nil { return err } Try / catch
err := store.Delete(ctx, req)
if err != nil && strings.Contains(err.Error(), "delete: ") {
// ResolveDeletionSetInTx failed before any write; safe to retry whole op
return retryWithFreshTx(req)
} Prevention
- Budget generous timeouts for deep cascades
- Use DryRun to precompute cascade size before committing
- Keep dependency edges clean (no orphaned rows) so closure walks stay cheap
- Retry freely: the set is resolved before any write, so failures leave no partial state
When it happens
Trigger: DeleteInTx calls ResolveDeletionSetInTx(ids, req.Cascade) and the closure expansion fails — driver error during the transitive dependency walk, cancelled context, or a backend query failure while expanding the cascade.
Common situations: Deep cascade graphs hitting context timeouts; connection loss mid-closure; corrupted dependency edges stalling expansion; schema drift on dependency tables.
Related errors
- delete: resolve ids: %w
- delete: check dependents: %w
- failed to remove federation peer: %w
- db: DependencySQLRepository.DeleteAllForIDs rows affected: %
- db: EventsSQLRepository.DeleteAllForIDs from %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/9bbb0a9275d26824.
Report an issue: GitHub.