gastownhall/beads · error
search %s (hydrate): %w
Error message
search %s (hydrate): %w
What it means
searchTablePatternBT wraps a failure of the Pattern B batch-fetch query (SELECT <columns> FROM <table> WHERE id IN (...)) executed after the id-only scan. The cheap id scan succeeded but re-fetching full rows for those ids failed. The table name is interpolated; the ids are bound as parameters, so the SQL itself is fixed — the cause is a driver/DB-level failure of that SELECT.
Source
Thrown at internal/storage/issueops/search.go:460
// Batch-fetch full rows from the known table (no wispSet partition needed).
placeholders := make([]string, len(ids))
fetchArgs := make([]interface{}, len(ids))
for i, id := range ids {
placeholders[i] = "?"
fetchArgs[i] = id
}
fetchFrom := tables.Main
if proj.joinLeases {
fetchFrom += " " + sqlbuild.LeaseJoin(tables.Main)
}
//nolint:gosec // G201: column expression and table name are fixed; ids are parameterized.
fetchSQL := fmt.Sprintf(`SELECT %s FROM %s WHERE id IN (%s)`,
proj.columns(tables), fetchFrom, strings.Join(placeholders, ","))
fetchRows, err := tx.QueryContext(ctx, fetchSQL, fetchArgs...)
if err != nil {
return nil, fmt.Errorf("search %s (hydrate): %w", tables.Main, err)
}
itemMap := make(map[string]T, len(ids))
for fetchRows.Next() {
item, scanErr := proj.scan(fetchRows)
if scanErr != nil {
_ = fetchRows.Close()
return nil, fmt.Errorf("search %s (hydrate): scan: %w", tables.Main, scanErr)
}
itemMap[proj.id(item)] = item
}
_ = fetchRows.Close()
if err := fetchRows.Err(); err != nil {
return nil, fmt.Errorf("search %s (hydrate): rows: %w", tables.Main, err)
}
// Reorder to preserve the id-scan ORDER BY.
results := make([]T, 0, len(ids))View on GitHub (pinned to 71377f2769)
Solutions
- Read the wrapped driver error; if it's a parameter-count or query-size limit, lower filter.Limit or set filter.NoIDShrink to force the single-query path
- Confirm the main issues/wisps table still exists and schema is current (bd migrate / bd doctor)
- Check DB connectivity and that no other process holds an exclusive lock
- If ctx deadline was exceeded, raise the timeout or reduce the limit
- Retry the operation on a new transaction/connection
Example fix
// before: huge limit -> enormous IN clause -> driver parameter limit filter.Limit = 100000 ids, err := searchIssues(ctx, tx, q, filter) // after: keep Pattern B within driver limits or opt out of id-shrink filter.Limit = 500 // or: filter.NoIDShrink = true // single wide query, no IN fetch ids, err := searchIssues(ctx, tx, q, filter)
Defensive patterns
Strategy: validation
Validate before calling
// keep the IN list within driver parameter limits before a Pattern B search
const maxParams = 900 // conservative for SQLite
if filter.Limit > maxParams && !filter.NoIDShrink {
filter.NoIDShrink = true // single wide query instead of IN (...) fetch
} Type guard
func isBatchFetchErr(err error) bool {
if err == nil { return false }
s := err.Error()
return strings.Contains(s, "(hydrate)") && !strings.Contains(s, "scan:") && !strings.Contains(s, "rows:")
} Try / catch
res, err := searchIssues(ctx, tx, q, filter)
if err != nil && isBatchFetchErr(err) {
// fall back to the non-shrink single-query path
filter.NoIDShrink = true
res, err = searchIssues(ctx, tx, q, filter)
} Prevention
- Keep filter.Limit under the driver's parameter limit when id-shrink is active
- Set NoIDShrink for very large limits
- Keep the connection alive across both queries of the pattern (same tx, healthy conn)
- Run bd doctor to confirm table/schema health before bulk searches
When it happens
Trigger: searchTablePatternBT runs (wide projection + filter.Limit > 0 + !NoIDShrink), the id scan returns ids, and tx.QueryContext on the fetch SQL errors — e.g. table missing/dropped between the two queries, connection dropped, ctx cancelled, or too many IN placeholders exhausting a driver limit.
Common situations: Very large filter.Limit producing an IN list exceeding driver parameter limits (SQLite default 999/32766); connection reset mid-search; database file locked by another process; schema changed (table dropped) between the id scan and fetch.
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/a8b88bdd128815c6.
Report an issue: GitHub.