gastownhall/beads · error

collectConnected (wisps): %w

Error message

collectConnected (wisps): %w

What it means

This error wraps a failure from depRepo.ListByIssueIDs with UseWispsTable=true and Direction=DepDirectionBoth in collectConnectedIssues. A missing wisps table is tolerated (IsTableNotExist short-circuits); any other failure aborts the delete or preview, since connected-issue detection would be incomplete.

Source

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

	return preview, nil
}

func (u *issueUseCaseImpl) collectConnectedIssues(
	ctx context.Context, allIDs []string, deletedSet map[string]bool,
) (map[string]*types.Issue, map[string]bool, error) {
	out := map[string]*types.Issue{}
	isWisp := map[string]bool{}
	if len(allIDs) == 0 {
		return out, isWisp, nil
	}

	issueRes, err := u.depRepo.ListByIssueIDs(ctx, allIDs, DepListOpts{Direction: DepDirectionBoth})
	if err != nil {
		return nil, nil, fmt.Errorf("collectConnected (issues): %w", err)
	}
	wispRes, err := u.depRepo.ListByIssueIDs(ctx, allIDs, DepListOpts{Direction: DepDirectionBoth, UseWispsTable: true})
	if err != nil && !dberrors.IsTableNotExist(err) {
		return nil, nil, fmt.Errorf("collectConnected (wisps): %w", err)
	}

	neighbors := map[string]bool{}
	accumulate := func(m map[string][]*types.Dependency) {
		for _, deps := range m {
			for _, d := range deps {
				for _, candidate := range [2]string{d.IssueID, d.DependsOnID} {
					if candidate == "" || deletedSet[candidate] {
						continue
					}
					neighbors[candidate] = true
				}
			}
		}
	}
	accumulate(issueRes.Outgoing)
	accumulate(issueRes.Incoming)
	accumulate(wispRes.Outgoing)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Resolve the wrapped driver error first (connectivity, locks, timeouts).
  2. Retry the operation; reads are safe and deletes are idempotent per ID.
  3. Run migrations if the wisps schema drifted from the installed version.
  4. If wisps are unused, the expected table-not-exist path is skipped silently — a surfaced error means something else failed.
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure wisps schema is migrated or intentionally absent before deletes:
if err := migrate(ctx); err != nil {
    return fmt.Errorf("wisps schema check failed: %w", err)
}

Type guard

if dberrors.IsTableNotExist(errors.Unwrap(err)) {
    return nil // tolerated: no wisps dependency table
}
var dbErr *dberrors.DBError
if errors.As(err, &dbErr) {
    // real driver failure — handle or propagate
}

Try / catch

res, err := uc.DeleteWisps(ctx, ids, opts)
if err != nil && strings.Contains(err.Error(), "collectConnected (wisps)") {
    if !dberrors.IsTableNotExist(errors.Unwrap(err)) {
        return fmt.Errorf("wisp connectivity scan failed: %w", err)
    }
}

Prevention

When it happens

Trigger: deleteMany (with text-reference rewriting) or previewDelete when the wisps dependency table exists but the bidirectional query fails: connection loss, timeout, schema/scan mismatch.

Common situations: Wisp table locked by concurrent writers; upgrade-related schema drift; transient DB failures during large cascade operations.

Related errors


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