gastownhall/beads · error

delete: list wisp dependents: %w

Error message

delete: list wisp dependents: %w

What it means

This error wraps a failure from depRepo.ListByIssueIDs with UseWispsTable=true and Direction=DepDirectionIn in externalDependents. Unlike the issue-table query, a table-not-exist error is tolerated (wisps may never have been created in this DB); any other failure aborts the delete/preview flow.

Source

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

	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
	}

	issueRes, err := u.depRepo.ListByIssueIDs(ctx, ids, DepListOpts{Direction: DepDirectionIn})
	if err != nil {
		return nil, fmt.Errorf("delete: list dependents: %w", err)
	}
	wispRes, err := u.depRepo.ListByIssueIDs(ctx, ids, DepListOpts{Direction: DepDirectionIn, UseWispsTable: true})
	if err != nil && !dberrors.IsTableNotExist(err) {
		return nil, fmt.Errorf("delete: list wisp dependents: %w", err)
	}

	out := map[string][]string{}
	seen := map[string]map[string]bool{}
	for _, res := range []DepBulkResult{issueRes, wispRes} {
		for target, deps := range res.Incoming {
			for _, d := range deps {
				if d.IssueID == "" || idSet[d.IssueID] {
					continue
				}
				if seen[target] == nil {
					seen[target] = map[string]bool{}
				}
				if seen[target][d.IssueID] {
					continue
				}
				seen[target][d.IssueID] = true
				out[target] = append(out[target], d.IssueID)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped error and exclude dberrors.IsTableNotExist cases (those are expected and ignored by design).
  2. Fix the underlying connection/timeout issue and retry the delete.
  3. Run a DB consistency/migration check if the wisps table exists but queries fail on schema.
  4. If wisps are unused in your deployment, confirm the table truly doesn't exist — that path is silently skipped.
Defensive patterns

Strategy: try-catch

Validate before calling

// Only surface unexpected errors; missing wisps table is tolerated upstream:
// ensure your DB either has the wisps tables migrated or none at all.
if err := migrate(ctx); err != nil {
    return fmt.Errorf("schema check failed: %w", err)
}

Type guard

var dbErr *dberrors.DBError
if errors.As(err, &dbErr) && !dberrors.IsTableNotExist(err) {
    // real failure, not the tolerated missing-wisps-table case
}

Try / catch

_, err := uc.DeleteWisps(ctx, ids, opts)
if err != nil && strings.Contains(err.Error(), "delete: list wisp dependents") {
    if dberrors.IsTableNotExist(errors.Unwrap(err)) {
        return nil // tolerated by design
    }
    return err
}

Prevention

When it happens

Trigger: deleteMany -> externalDependents when the wisps dependency table exists but the incoming-dependency query against it fails for reasons other than table-not-exist: connection error, timeout, scan failure.

Common situations: Wisps table present but locked or corrupted; connection dropped mid-query; driver version mismatch after a beads upgrade altering the wisps schema.

Related errors


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