gastownhall/beads · error

search union with counts: %w

Error message

search union with counts: %w

What it means

Wraps the database/sql error from QueryContext running the combined issues+wisps UNION ALL query. At this point SQL text and args are fully built, so the failure is at the engine: syntax, unknown table/column, connection loss, or placeholder/arg mismatch (iArgs then wArgs must match the leg order).

Source

Thrown at internal/storage/domain/db/issue_search_counts.go:89

	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)
	}
	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)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped driver error for the concrete engine message
  2. Verify both issues and wisps tables exist and match the expected schema (bd doctor / schema migration)
  3. Retry on transient network errors; increase context timeout for large searches
  4. If it's an arg/placeholder mismatch, ensure buildUnionSubquery argument counts match each leg's placeholders

Example fix

// before
ctx := context.Background()
// after
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() // and retry once on transient net errors
Defensive patterns

Strategy: retry

Validate before calling

if err := ensureSchemaCurrent(ctx, db); err != nil {
    return fmt.Errorf("run bd migrations first: %w", err)
}

Try / catch

page, err := store.SearchWithCounts(ctx, q, filter)
if err != nil && isTransient(err) {
    select {
    case <-time.After(backoff):
        page, err = store.SearchWithCounts(ctx, q, filter)
    default:
    }
}

Prevention

When it happens

Trigger: QueryContext on the union SQL fails — driver-level error such as 'no such table', SQL syntax error, too many parameters, or context cancellation/timeouts mid-query.

Common situations: Dolt/SQLite schema missing wisp tables probed as existing earlier, oversized result sets hitting parameter or packet limits, network drop to remote Dolt server, or query cancelled by client timeout.

Related errors


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