gastownhall/beads · error
search union with counts (wisps): %w
Error message
search union with counts (wisps): %w
What it means
Same wrapper as the issues leg but for the wisps (ephemeral) side of the UNION ALL. The wisps leg uses wispsFilterTables and alias 'w'; any failure rendering that subquery is tagged 'wisps' so you know the ephemeral half failed, not the durable half. The leg carries its own parenthesized ORDER BY/LIMIT window, which is required syntax for pushing the window down into a UNION ALL.
Source
Thrown at internal/storage/domain/db/issue_search_counts.go:72
}
return finishSearchCountsPage(out, filter)
}
return r.searchUnionWithCounts(ctx, query, filter, wispDepsExist)
}
func (r *issueSQLRepositoryImpl) searchUnionWithCounts(ctx context.Context, query string, filter types.IssueFilter, wispDepsExist bool) (domain.SearchCountsPage, error) {
outerOrderBy := unionOrderBySQL(filter.SortBy, filter.SortDesc)
window := searchWindowForFilter(filter)
legWindow := legWindowSQL(outerOrderBy, window)
iSub, iArgs, err := r.buildUnionSubquery(query, filter, issuesFilterTables, "i", legWindow)
if err != nil {
return domain.SearchCountsPage{}, fmt.Errorf("search union with counts (issues): %w", err)
}
wSub, wArgs, err := r.buildUnionSubquery(query, filter, wispsFilterTables, "w", legWindow)
if err != nil {
return domain.SearchCountsPage{}, fmt.Errorf("search union with counts (wisps): %w", err)
}
// EACH LEG IS PARENTHESIZED, and it is not decoration. A leg that carries
// its own ORDER BY and LIMIT (legWindowSQL) is a syntax error inside a bare
// UNION ALL — the engine reads the clause as belonging to the union — so the
// parentheses are what let the window be pushed down at all.
//nolint:gosec // G201: subqueries built from hardcoded table names and ? placeholders.
unionSQL := fmt.Sprintf("SELECT id, src FROM ((%s) UNION ALL (%s)) merged %s %s",
iSub, wSub, outerOrderBy, window.sql)
args := make([]any, 0, len(iArgs)+len(wArgs))
args = append(args, iArgs...)
args = append(args, wArgs...)
rows, err := r.runner.QueryContext(ctx, unionSQL, args...)
if err != nil {
return domain.SearchCountsPage{}, fmt.Errorf("search union with counts: %w", err)
}View on GitHub (pinned to 71377f2769)
Solutions
- Read the wrapped cause to identify the failing clause
- Remove or adjust filter fields unsupported for wisps, or set filter.Ephemeral=false / filter.SkipWisps to skip the wisp leg
- Verify wispsFilterTables covers every filter field your query uses
- Upgrade/rebuild if schema is stale (wisps tables vs code expectations)
Example fix
// before
filter := types.IssueFilter{IncludeEphemeral: true, SomeIssueOnlyField: x}
// after
filter.SkipWisps = true // or drop the issue-only field Defensive patterns
Strategy: validation
Validate before calling
if includesWispLeg(filter) && requiresIssueOnlyColumns(filter) {
return fmt.Errorf("filter field not supported for wisps; set SkipWisps or drop the field")
} Try / catch
page, err := store.SearchWithCounts(ctx, q, filter)
if err != nil && strings.Contains(err.Error(), "(wisps)") {
filter.SkipWisps = true
page, err = store.SearchWithCounts(ctx, q, filter)
} Prevention
- Keep wispsFilterTables in sync whenever a filter field is added
- Test wisp-leg searches for every filter flag
- Prefer SkipWisps when ephemeral issues are irrelevant to the query
When it happens
Trigger: searchUnionWithCounts building the wisp leg via buildUnionSubquery(query, filter, wispsFilterTables, "w", legWindow) fails — e.g. a filter field not renderable against wisp tables.
Common situations: Searching with a filter that assumes durable-issue-only columns (or vice versa) while wisps exist and are included; schema drift between issues and wisps filter table definitions.
Related errors
- search union with counts (issues): %w
- search wisps (ephemeral filter): %w
- search issues: %w
- search %s: %w
- search union with counts (hydrate issues): %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/04eda517280c00c9.
Report an issue: GitHub.