gastownhall/beads · error
search count %s: %w
Error message
search count %s: %w
What it means
scanCountsRowsInTx wraps a failure of the counts search query (tx.QueryContext) with the main table name. This is the ready-counts / search-with-counts projection query built by the sqlbuild builder; a failure here means the SELECT with count joins never produced a result set. The query string is builder-produced and user input rides ? placeholders, so the cause is a DB-level error, not SQL injection surface.
Source
Thrown at internal/storage/issueops/search_counts.go:159
return out, nil
}
//nolint:gosec // G201: SQL fragments are caller-built from hardcoded shapes
func runSearchQueryInTx(ctx context.Context, tx *sql.Tx, tables FilterTables, whereSQL, orderBySQL, limitSQL string, args []interface{}, includeWispReverseDeps bool, hyd sqlbuild.CountsHydration) ([]*types.IssueWithCounts, error) {
searchSQL, _ := sqlbuild.SearchCountsSQL(tables, nil, whereSQL, orderBySQL, limitSQL, includeWispReverseDeps, hyd)
return scanCountsRowsInTx(ctx, tx, tables.Main, searchSQL, args, hyd)
}
// scanCountsRowsInTx runs a prebuilt counts mega-query and hydrates each row
// through ScanReadyWorkRowWithCounts, deduping by issue ID. It is the single
// scan/dedupe loop shared by the predicate-form search path and the by-IDs
// ready-counts path.
//
//nolint:gosec // G201: query is builder-produced; user input rides ? placeholders.
func scanCountsRowsInTx(ctx context.Context, tx *sql.Tx, mainTable, query string, args []interface{}, hyd sqlbuild.CountsHydration) ([]*types.IssueWithCounts, error) {
rows, err := tx.QueryContext(ctx, query, args...)
if err != nil {
return nil, fmt.Errorf("search count %s: %w", mainTable, err)
}
defer func() { _ = rows.Close() }()
var out []*types.IssueWithCounts
seen := make(map[string]bool)
for rows.Next() {
iwc, scanErr := ScanReadyWorkRowWithCounts(rows, hyd)
if scanErr != nil {
return nil, scanErr
}
if iwc == nil || iwc.Issue == nil {
continue
}
if seen[iwc.Issue.ID] {
continue
}
seen[iwc.Issue.ID] = true
out = append(out, iwc)View on GitHub (pinned to 71377f2769)
Solutions
- Unwrap the driver error; for 'no such table', run schema migration (bd migrate / bd doctor)
- Validate the query/sort expression (bd query '<expr>' syntax) if the cause is an SQL syntax error
- Check for concurrent writers/locks and retry
- Raise the context timeout or reduce the filter scope
- If the SQL build itself is at fault, check the sqlbuild version matches your storage layer
Example fix
// before: old DB missing table joined by the counts projection
res, err := issueops.ReadyIssuesWithCounts(ctx, filter) // 'no such table: wisp_dependencies'
// after: bring schema current first
if err := store.Migrate(ctx); err != nil { return err }
res, err := issueops.ReadyIssuesWithCounts(ctx, filter) Defensive patterns
Strategy: validation
Validate before calling
// validate schema and expression before a counts query
if err := store.ValidateSchema(ctx); err != nil {
return fmt.Errorf("run bd migrate: %w", err)
}
if _, _, err := issueops.BuildIssueFilterClauses(query, filter, tables); err != nil {
return fmt.Errorf("invalid query/filter: %w", err)
} Type guard
func isCountsQueryErr(err error) bool {
if err == nil { return false }
for _, t := range []string{"issues", "wisps"} {
if strings.HasPrefix(err.Error(), "search count "+t+": ") {
return !strings.Contains(err.Error(), ": rows: ")
}
}
return false
} Try / catch
res, err := issueops.SearchIssuesWithCounts(ctx, q, filter)
if err != nil && isCountsQueryErr(err) {
if strings.Contains(err.Error(), "no such table") {
if migErr := store.Migrate(ctx); migErr != nil { return migErr }
res, err = issueops.SearchIssuesWithCounts(ctx, q, filter)
}
if err != nil { return err }
} Prevention
- Migrate after every binary upgrade so joined tables exist
- Validate bd query expressions and sort keys before running
- Avoid count searches during exclusive-lock bulk operations
- Keep sqlbuild and storage layers version-aligned
When it happens
Trigger: runReadyCountsInTx or runSearchQueryInTx invokes scanCountsRowsInTx and the count-projection SELECT fails — missing table (e.g. wisps on old schemas), bad sort/filter expression reaching ORDER BY, DB locked, connection dropped, or ctx cancelled.
Common situations: bd ready / bd list --counts against an older DB missing a joined optional table; an invalid sort key or query expression producing invalid SQL; write-lock contention during a bulk import; remote Dolt outage.
Understand the failure class
Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.
Related errors
- failed to get federation peer: %w
- failed to list federation peers: %w
- iter dependents: query: %w
- db: ChildCounterSQLRepository.NextChildID: scan existing chi
- db: CommentSQLRepository.CountsByIssueIDs: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/b76f7a67d5a313df.
Report an issue: GitHub.