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.DeletedCount

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped error from ResolveDeletionSetInTx for the real cause
  2. Retry with a fresh transaction; nothing was written, so retry is safe
  3. For deep cascades, raise the context timeout or delete the graph in stages
  4. Verify dependency-table integrity (orphaned/looping edges) if failures recur on the same ids
  5. 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

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


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