{"record":{"id":"0f7caa8a9d4a1f01","repo":"gastownhall/beads","slug":"search-union-with-counts-issues-w","errorCode":null,"errorMessage":"search union with counts (issues): %w","messagePattern":"search union with counts \\(issues\\): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/issue_search_counts.go","lineNumber":68,"sourceCode":"\tif empty || !wispDepsExist {\n\t\tout, err := r.runFilterSearchQuery(ctx, query, filter, issuesFilterTables, wispDepsExist)\n\t\tif err != nil {\n\t\t\treturn domain.SearchCountsPage{}, err\n\t\t}\n\t\treturn finishSearchCountsPage(out, filter)\n\t}\n\n\treturn r.searchUnionWithCounts(ctx, query, filter, wispDepsExist)\n}\n\nfunc (r *issueSQLRepositoryImpl) searchUnionWithCounts(ctx context.Context, query string, filter types.IssueFilter, wispDepsExist bool) (domain.SearchCountsPage, error) {\n\touterOrderBy := unionOrderBySQL(filter.SortBy, filter.SortDesc)\n\twindow := searchWindowForFilter(filter)\n\tlegWindow := legWindowSQL(outerOrderBy, window)\n\n\tiSub, iArgs, err := r.buildUnionSubquery(query, filter, issuesFilterTables, \"i\", legWindow)\n\tif err != nil {\n\t\treturn domain.SearchCountsPage{}, fmt.Errorf(\"search union with counts (issues): %w\", err)\n\t}\n\twSub, wArgs, err := r.buildUnionSubquery(query, filter, wispsFilterTables, \"w\", legWindow)\n\tif err != nil {\n\t\treturn domain.SearchCountsPage{}, fmt.Errorf(\"search union with counts (wisps): %w\", err)\n\t}\n\n\t// EACH LEG IS PARENTHESIZED, and it is not decoration. A leg that carries\n\t// its own ORDER BY and LIMIT (legWindowSQL) is a syntax error inside a bare\n\t// UNION ALL — the engine reads the clause as belonging to the union — so the\n\t// parentheses are what let the window be pushed down at all.\n\t//nolint:gosec // G201: subqueries built from hardcoded table names and ? placeholders.\n\tunionSQL := fmt.Sprintf(\"SELECT id, src FROM ((%s) UNION ALL (%s)) merged %s %s\",\n\t\tiSub, wSub, outerOrderBy, window.sql)\n\n\targs := make([]any, 0, len(iArgs)+len(wArgs))\n\targs = append(args, iArgs...)\n\targs = append(args, wArgs...)\n","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/issue_search_counts.go#L50-L86","documentation":"This error wraps any failure from buildUnionSubquery when constructing the issues-side leg of the issues+wisps UNION ALL search-with-counts query. buildUnionSubquery assembles the per-leg SELECT (filter clauses, window, ORDER BY/LIMIT) and can fail when filter clauses cannot be rendered for the issues filter tables. The wrapper tags the leg so callers can tell which half of the union broke.","triggerScenarios":"Calling search (with counts) across issues and wisps when the issues leg subquery build fails — typically an unsupported filter combination or an error surfaced from buildIssueFilterClauses inside buildUnionSubquery.","commonSituations":"Passing an IssueFilter with a field that has no SQL rendering for the issues table set, or an invalid sort/limit combination, during bd search/list queries that span both durable issues and ephemeral wisps.","solutions":["Inspect the wrapped cause (%w) to see which filter clause failed to build","Simplify or correct the IssueFilter fields (sort, status, labels, etc.) that the cause names","Set filter.SkipWisps or use the non-union path if wisps are irrelevant","If the failure is on an internal alias/clause, check buildIssueFilterClauses for unsupported qualifiers"],"exampleFix":"// before\nfilter := types.IssueFilter{SortBy: \"weird_field\"}\n// after\nfilter := types.IssueFilter{SortBy: \"priority\"} // supported sort field","handlingStrategy":"validation","validationCode":"if filter.SortBy != \"\" && !validSortFields[filter.SortBy] {\n    return fmt.Errorf(\"unsupported sort field %q\", filter.SortBy)\n}","typeGuard":null,"tryCatchPattern":"page, err := store.SearchWithCounts(ctx, q, filter)\nif err != nil {\n    var buildErr *fmt.WrapError // inspect unwrapped cause\n    log.Printf(\"union issues-leg build failed: %v\", err)\n    filter.SkipWisps = true\n    page, err = store.SearchWithCounts(ctx, q, filter)\n}","preventionTips":["Only use documented sort/filter fields on IssueFilter","Cover issues+wisps union search in integration tests with all filter fields","When adding a filter field, update both issuesFilterTables and wispsFilterTables"],"tags":["sql","search","query-building"],"backgroundTag":"query-build-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}