gastownhall/beads · error

delete: rewrite text references: %w

Error message

delete: rewrite text references: %w

What it means

Wraps failure of rewriteTextReferences, the optional step (DeleteIssuesParams.UpdateTextReferences) that rewrites textual references (e.g. in descriptions) of issues connected to the deleted set. Unlike the row deletes this happens after rows are already removed, so a failure leaves deletions applied but references possibly stale; the result and error are still returned.

Source

Thrown at internal/storage/domain/issue_delete.go:209

	}
	if _, err := u.eventsRepo.DeleteAllForIDs(ctx, wispIDs, RecordEventOpts{UseWispsTable: true}); err != nil {
		return result, fmt.Errorf("delete: drop wisp events: %w", err)
	}

	issuesDeleted, err := u.issueRepo.DeleteByIDs(ctx, regularIDs, IssueTableOpts{})
	if err != nil {
		return result, fmt.Errorf("delete: drop issue rows: %w", err)
	}
	wispsDeleted, err := u.issueRepo.DeleteByIDs(ctx, wispIDs, IssueTableOpts{UseWispsTable: true})
	if err != nil {
		return result, fmt.Errorf("delete: drop wisp rows: %w", err)
	}
	result.DeletedCount = issuesDeleted + wispsDeleted

	if params.UpdateTextReferences && len(connected) > 0 {
		refs, err := u.rewriteTextReferences(ctx, allIDs, connected, connectedIsWisp, actor)
		if err != nil {
			return result, fmt.Errorf("delete: rewrite text references: %w", err)
		}
		result.ReferencesUpdated = refs
	}

	if err := u.issueRepo.RecomputeIsBlocked(ctx, affectedIssues, affectedWisps); err != nil {
		return result, fmt.Errorf("delete: recompute is_blocked: %w", err)
	}

	return result, nil
}

// externalDependents finds the direct dependents of each id in ids that are
// not themselves in ids, across both the issue and wisp dependency tables.
// The result maps deletion-set id -> external dependent ids (unsorted).
func (u *issueUseCaseImpl) externalDependents(ctx context.Context, ids []string) (map[string][]string, error) {
	idSet := make(map[string]bool, len(ids))
	for _, id := range ids {
		idSet[id] = true

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped cause for the specific SQL/read error and fix it.
  2. Retry the delete with UpdateTextReferences:true, or verify/re-run reference rewriting separately since rows are already deleted.
  3. Increase the context deadline for large connected sets.
  4. Reduce the deletion batch size to shrink the connected-issue graph.

Example fix

// before
res, err := store.DeleteIssues(ctx, ids, DeleteIssuesParams{UpdateTextReferences: true})
// after
res, err := store.DeleteIssues(ctx, ids, DeleteIssuesParams{UpdateTextReferences: true})
if err != nil && strings.Contains(err.Error(), "rewrite text references") {
    log.Printf("rows deleted but references may be stale: %v", err) // re-run rewrite manually
}
Defensive patterns

Strategy: try-catch

Validate before calling

// gate the flag on expected graph size; skip when the connected graph is huge
if params.UpdateTextReferences && len(ids) > largeThreshold {
    params.UpdateTextReferences = false // rewrite references in a separate controlled pass
}

Try / catch

res, err := store.DeleteIssues(ctx, ids, opts)
if err != nil {
    if strings.Contains(err.Error(), "delete: rewrite text references") {
        // rows ARE deleted; schedule a reference-rewrite backfill instead of retrying the delete
        scheduleReferenceRewrite(ids)
    }
}

Prevention

When it happens

Trigger: Calling DeleteIssue/DeleteIssues with UpdateTextReferences:true when scanning or updating text of connected issues fails (SQL error on read/update, missing columns/tables, connection loss).

Common situations: Very large connected graphs making the rewrite slow and prone to timeouts; schema drift in embedded DB; context cancelled mid-rewrite; concurrent edits to the referencing issues.

Related errors


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