gastownhall/beads · error
search union with counts (hydrate wisps): %w
Error message
search union with counts (hydrate wisps): %w
What it means
Same as the issues hydration wrapper but for wisp IDs. Unlike issues, a missing optional wisp table is tolerated (missingOptionalWispTable); only real failures other than 'table missing' are wrapped and returned. Indicates the wisp-side counts mega-query failed for a non-missing-table reason.
Source
Thrown at internal/storage/domain/db/issue_search_counts.go:107
return domain.SearchCountsPage{}, fmt.Errorf("search union with counts: %w", err)
}
page, err := scanIDSrcPage(rows)
if err != nil {
return domain.SearchCountsPage{}, fmt.Errorf("search union with counts: %w", err)
}
page.sortGoSide(filter.SortBy, filter.SortDesc)
hasMore, err := page.finishWindow(window)
if err != nil {
return domain.SearchCountsPage{}, err
}
issuesByID, err := r.fetchCountsByIDs(ctx, page.issueIDs, issuesFilterTables, wispDepsExist, hydrationFor(filter))
if err != nil {
return domain.SearchCountsPage{}, fmt.Errorf("search union with counts (hydrate issues): %w", err)
}
wispsByID, err := r.fetchCountsByIDs(ctx, page.wispIDs, wispsFilterTables, true, hydrationFor(filter))
if err != nil && !missingOptionalWispTable(err) {
return domain.SearchCountsPage{}, fmt.Errorf("search union with counts (hydrate wisps): %w", err)
}
out := reassembleBySrc(page.ordered, issuesByID, wispsByID)
return domain.SearchCountsPage{Items: out, HasMore: hasMore}, nil
}
// hydrationFor reads the hydration opt-outs off a search filter, matching the
// store-backed path's helper of the same name (internal/storage/issueops).
// Both bodies must read the same fields off the same filter or a caller that
// set one would get different columns from the two backends.
func hydrationFor(filter types.IssueFilter) sqlbuild.CountsHydration {
return sqlbuild.CountsHydration{SkipLabels: filter.SkipLabels, SkipCounts: filter.SkipCounts, Lite: filter.Lite}
}
// readyHydrationFor is the WORK-filter twin, matching
// issueops.readyHydrationFor. See it for why a ready filter carries Lite and
// neither of the other two.
func readyHydrationFor(filter types.WorkFilter) sqlbuild.CountsHydration {View on GitHub (pinned to 71377f2769)
Solutions
- Read the inner wrapped error; if it IS a missing-table error this path would have succeeded, so look for a different cause
- Run migrations/doctor to reconcile wisp schema
- Use filter.Ephemeral=true to query wisps alone, or SkipWisps to avoid the leg
- Retry transient network/cancellation failures
Defensive patterns
Strategy: fallback
Validate before calling
if !wispTablesExist(ctx, db) {
filter.SkipWisps = true
} Try / catch
page, err := store.SearchWithCounts(ctx, q, filter)
if err != nil && strings.Contains(err.Error(), "hydrate wisps") {
filter.SkipWisps = true // tolerate wisp hydration failure
page, err = store.SearchWithCounts(ctx, q, filter)
} Prevention
- Ensure wisp schema is complete whenever wisp_dependencies exists
- Treat missing optional wisp tables as non-fatal (library already does)
- Test searches in DBs both with and without wisps tables
- Keep wisp pages small to limit hydration load
When it happens
Trigger: fetchCountsByIDs on page.wispIDs fails with an error that is not the tolerated missing-wisp-table sentinel — e.g. engine error, cancellation, or malformed counts query for wisp tables.
Common situations: wisp_dependencies exists but another wisp-related table is missing/incompatible, hydration of a large wisp page times out, or connection drop mid-hydration.
Related errors
- descendants: hydrate wisps: %w
- search union with counts (wisps): %w
- search union with counts (hydrate issues): %w
- ready work union with counts: hydrate wisps: %w
- count open wisp children for %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/850345abeaa6d187.
Report an issue: GitHub.