gastownhall/beads · error
search %s (hydrate): scan: %w
Error message
search %s (hydrate): scan: %w
What it means
searchTablePatternBT wraps a row-scan failure of the Pattern B batch-fetch: proj.scan could not decode a row of the SELECT ... WHERE id IN (...) fetch into the projection type. This means the query ran but a column's value could not be scanned (type mismatch, unexpected NULL, column count change).
Source
Thrown at internal/storage/issueops/search.go:468
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))
for _, id := range ids {
if item, ok := itemMap[id]; ok {
results = append(results, item)
}
}
if proj.hydrate != nil && len(results) > 0 {
if err := proj.hydrate(ctx, tx, tables, results, filter); err != nil {View on GitHub (pinned to 71377f2769)
Solutions
- Check the wrapped scan error for the offending column index/type; compare it to proj.columns(tables) for that projection
- Run schema migration to align the DB with the binary's expected schema (bd migrate / bd doctor)
- If NULLs are expected in your data, ensure the scan uses sql.NullString / nullable pointers
- Verify no manual schema edits diverged from beads' expected layout; restore from a clean export/import (bd export/import)
- If writing a custom projection, confirm columns() and scan() read the same columns in the same order
Example fix
// before: scanning a nullable column into string var closedAt string _ = rows.Scan(&id, &closedAt) // after: handle NULL var closedAt sql.NullString _ = rows.Scan(&id, &closedAt) issue.ClosedAt = nullableTime(closedAt)
Defensive patterns
Strategy: validation
Validate before calling
// verify schema version matches the binary before searching
if err := store.ValidateSchema(ctx); err != nil {
return fmt.Errorf("run bd migrate: %w", err)
} Type guard
func isScanErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "(hydrate): scan: ")
} Try / catch
res, err := searchIssues(ctx, tx, q, filter)
if err != nil && isScanErr(err) {
// scan failures usually mean schema drift: migrate, then retry once
if migErr := store.Migrate(ctx); migErr != nil { return migErr }
res, err = searchIssues(ctx, tx, q, filter)
} Prevention
- Always migrate the DB after upgrading the bd binary
- Never hand-edit schema columns; use bd export/import for reshaping
- Keep projections' columns() and scan() in sync when customizing
- Use nullable scan types (sql.NullString, pointers) for optional columns
When it happens
Trigger: A row returned by the batch fetch has a column whose Go type does not match the scan target — e.g. schema drift after an upgrade (a NOT NULL column became NULLable and is NULL), a projection/scan pair out of sync in code, or a value stored with an unexpected type in the DB.
Common situations: Running a newer bd binary against an older database (or vice versa) so column layout differs; a manually edited/migrated DB with NULLs where the scanner expects values; custom projections in tests whose columns() and scan() disagree.
Related errors
- %s: %w
- failed to scan federation peer: %w
- db: ChildCounterSQLRepository.NextChildID: scan: %w
- db: CommentSQLRepository.CountsByIssueIDs: scan: %w
- db: CommentSQLRepository.ListByIssueIDs: scan: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/6c42c1071c75c622.
Report an issue: GitHub.