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

  1. 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
  2. Confirm the main issues/wisps table still exists and schema is current (bd migrate / bd doctor)
  3. Check DB connectivity and that no other process holds an exclusive lock
  4. If ctx deadline was exceeded, raise the timeout or reduce the limit
  5. 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

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


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