gastownhall/beads · error
search %s: %w
Error message
search %s: %w
What it means
searchTableInTxT wraps a failure from the projection's post-search hydrate callback with the searched table name (tables.Main). The main row scan succeeded, but enriching the result rows (e.g. loading dependencies, labels, or other related data) inside the same transaction failed. This is a wrapper: the wrapped cause (err) carries the real failure.
Source
Thrown at internal/storage/issueops/search.go:420
id := proj.id(item)
if _, dup := seen[id]; dup {
continue // GH#3567: skip duplicate rows from dependency subqueries
}
seen[id] = struct{}{}
results = append(results, item)
}
_ = rows.Close()
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("search %s: rows: %w", tables.Main, err)
}
if goSideSort {
results = goSideSortAndTrim(results, proj.id, filter.SortDesc, eff)
}
if proj.hydrate != nil && len(results) > 0 {
if err := proj.hydrate(ctx, tx, tables, results, filter); err != nil {
return nil, fmt.Errorf("search %s: %w", tables.Main, err)
}
}
return results, nil
}
// searchTablePatternBT runs Pattern B for wide projections. It reuses the
// id-only search (idProjection) — byte-for-byte the non-hydrating query
// SearchIssueIDsInTx runs against this table — to get the ordered, LIMIT-bound
// id list, then batch-fetches the full projection for those ids and hydrates.
// Keeping the shrink scan in exactly one place (the id projection) is why this
// no longer hand-rolls its own SELECT id loop. Narrow projections never reach
// here: they leave idShrink false and are themselves the id scan.
func searchTablePatternBT[T any](ctx context.Context, tx DBTX, query string, filter types.IssueFilter, tables FilterTables, proj searchProjection[T]) ([]T, error) {
ids, err := searchTableInTxT(ctx, tx, query, filter, tables, idProjection)
if err != nil {
return nil, err
}View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped inner error (%w cause) — it names the actual hydration failure; fix that root cause first
- Verify all auxiliary tables the projection hydrates from exist (run schema migration, e.g. bd doctor / bd migrate)
- If the cause is context deadline/cancel, increase the timeout or reduce filter.Limit / MaxRows to shrink the result set
- Retry the search on a fresh transaction if the tx was poisoned by an earlier failed statement
- Check DB connectivity/locks (another process holding a write lock on Dolt/SQLite)
Example fix
// before: searching with a filter that hydrates deps against a schema missing the table
issues, err := issueops.SearchIssuesInTx(ctx, tx, "", filter)
// after: ensure schema is current first, and bound the work
if err := store.EnsureSchema(ctx); err != nil { return err }
filter.Limit = 100
issues, err := issueops.SearchIssuesInTx(ctx, tx, "", filter) Defensive patterns
Strategy: try-catch
Validate before calling
// before calling search: ensure auxiliary tables exist and tx is live
var one int
if err := tx.QueryRowContext(ctx, "SELECT 1 FROM dependencies LIMIT 1").Scan(&one); err != nil && !errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("hydration prerequisite missing: %w", err)
}
if ctx.Err() != nil { return ctx.Err() } Type guard
func isHydrateSearchErr(err error) (string, bool) {
if err == nil { return "", false }
msg := err.Error()
for _, table := range []string{"issues", "wisps", "wisp_dependencies"} {
if strings.HasPrefix(msg, "search "+table+": ") {
return table, true
}
}
return "", false
} Try / catch
results, err := issueops.SearchIssuesInTx(ctx, tx, q, filter)
if err != nil {
var table string
if isHydrateSearchErr(err) && strings.HasPrefix(err.Error(), table = "search ") {
// inner cause via errors.Unwrap / %w chain
return fmt.Errorf("search hydration failed for %s: %w", table, err)
}
return err
} Prevention
- Run schema migrations before searching (bd migrate / bd doctor)
- Keep context deadlines proportional to result-set size
- Use SkipLabels/SkipCounts/Lite filters when you do not need hydrated fields
- Retry transient driver errors with backoff on fresh transactions
When it happens
Trigger: Calling any search that routes through searchTableInTxT (directly or via getReadyWispsInTx, searchInTx, searchTablePatternBT) where proj.hydrate is non-nil, results are non-empty, and the hydration query fails — e.g. the hydration SQL hits a missing/corrupt auxiliary table, the tx was already broken by an earlier error, or ctx was cancelled mid-hydration.
Common situations: Running bd search/list against a database where an auxiliary table (dependencies, labels, etc.) was dropped or is from an older schema version; a DB lock/timeout during hydration; context deadline exceeded on large result sets; a corrupted transaction after a prior failed statement.
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
- search issues: %w
- search %s: %w
- search union with counts (hydrate issues): %w
- ready work union with counts: hydrate wisps: %w
- get dependencies: fetch issues: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f28aac0648e7c924.
Report an issue: GitHub.