gastownhall/beads · error

search wisps (ephemeral filter): %w

Error message

search wisps (ephemeral filter): %w

What it means

searchAcrossIssuesAndWisps wraps a failure from searchTable against the wisps table when the caller set filter.Ephemeral=true. When Ephemeral is set, only the wisps table is searched first; an error that missingOptionalWispTable does not classify as a legitimately-absent optional wisp table (e.g. connection failure, a missing non-optional table like the search FROM's lease join) is wrapped and returned instead of being treated as "no wisps".

Source

Thrown at internal/storage/domain/db/issue_search.go:43

	issuesFilterTables = sqlbuild.IssuesFilterTables
	wispsFilterTables  = sqlbuild.WispsFilterTables
)

// missingOptionalWispTable reports whether err is the absence of a wisp-plane
// table a database may legitimately not have. A wisp search touches more than
// that: its FROM clause carries sqlbuild.LeaseJoin and its hydration reads
// wisp_labels, so a blanket table-not-exist check reports a broken database as
// an empty wisp plane and silently drops live rows from the result.
func missingOptionalWispTable(err error) bool {
	name, ok := dberrors.MissingTableName(err)
	return ok && sqlbuild.OptionalWispTable(name)
}

func (r *issueSQLRepositoryImpl) searchAcrossIssuesAndWisps(ctx context.Context, query string, filter types.IssueFilter) (domain.SearchPage, error) {
	if filter.Ephemeral != nil && *filter.Ephemeral {
		page, err := r.searchTable(ctx, query, filter, wispsFilterTables)
		if err != nil && !missingOptionalWispTable(err) {
			return domain.SearchPage{}, fmt.Errorf("search wisps (ephemeral filter): %w", err)
		}
		if len(page.Items) > 0 {
			return page, nil
		}
	}

	if filter.SkipWisps {
		page, err := r.searchTable(ctx, query, filter, issuesFilterTables)
		if err != nil {
			return domain.SearchPage{}, fmt.Errorf("search issues: %w", err)
		}
		return page, nil
	}

	empty, probeErr := r.wispsTableEmptyOrMissing(ctx)
	if probeErr != nil {
		return domain.SearchPage{}, fmt.Errorf("search wisps (merge): probe: %w", probeErr)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped inner error and dberrors.MissingTableName to see exactly which table or failure is involved
  2. Run bd doctor to verify wisp-plane tables (wisps, wisp_labels) and the leases table exist
  3. Retry if the inner error is a transient connection failure
  4. Drop Ephemeral=true and search both planes normally if you do not strictly need ephemeral-only results

Example fix

// before
page, err := store.Search(ctx, q, types.IssueFilter{Ephemeral: &trueVal})
// after: search both planes unless ephemeral-only is a hard requirement
page, err := store.Search(ctx, q, types.IssueFilter{})
Defensive patterns

Strategy: type-guard

Type guard

// mirror the library's own classification
func isOptionalWispTableMissing(err error) bool {
    name, ok := dberrors.MissingTableName(err)
    return ok && sqlbuild.OptionalWispTable(name)
}

Try / catch

page, err := store.Search(ctx, q, types.IssueFilter{Ephemeral: &t})
if err != nil && strings.Contains(err.Error(), "search wisps (ephemeral filter)") {
    if !isOptionalWispTableMissing(errors.Unwrap(err)) {
        // real breakage: repair or fall back to a full search
        page, err = store.Search(ctx, q, types.IssueFilter{})
    }
}

Prevention

When it happens

Trigger: Calling SearchAcrossIssuesAndWisps (bd query/search) with Ephemeral=true when the wisps search fails on something other than a missing optional table: broken connection, missing leases table used in the FROM join, engine error, or a genuinely broken wisp schema.

Common situations: Ephemeral-only search during a Dolt outage; a database where wisps exists but wisp_labels (needed for hydration) is missing — the code's own comment warns a blanket table-not-exist tolerance would mask this as an empty result, so real breakage surfaces here.

Related errors


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