gastownhall/beads · error
search %s: scan: %w
Error message
search %s: scan: %w
What it means
Raised when rows.Scan (inside scanIssue) fails while iterating search result rows. Scan fails when a column's value cannot be converted into the destination Go type — e.g. NULL in a NOT-NULL-assumed column, a malformed value, or driver/column-count mismatch. The search wraps it as "search <table>: scan: <err>" and closes the rows before returning.
Source
Thrown at internal/storage/domain/db/issue_search.go:269
orderBy := orderBySQL(filter.SortBy, filter.SortDesc, "")
window := searchWindowForFilter(filter)
//nolint:gosec // G201: SQL fragments from fixed table names and parameterized filters.
querySQL := fmt.Sprintf(`%s%s FROM %s %s %s %s %s`,
selectKw, issueSelectColumns, plan.FromSQL, sqlbuild.LeaseJoin(tables.Main), whereSQL, orderBy, window.sql)
rows, err := r.runner.QueryContext(ctx, querySQL, args...)
if err != nil {
return domain.SearchPage{}, fmt.Errorf("search %s: %w", tables.Main, err)
}
var issues []*types.Issue
seen := make(map[string]bool)
for rows.Next() {
issue, scanErr := scanIssue(rows)
if scanErr != nil {
_ = rows.Close()
return domain.SearchPage{}, fmt.Errorf("search %s: scan: %w", tables.Main, scanErr)
}
if seen[issue.ID] {
continue
}
seen[issue.ID] = true
issues = append(issues, issue)
}
_ = rows.Close()
if err := rows.Err(); err != nil {
return domain.SearchPage{}, fmt.Errorf("search %s: rows: %w", tables.Main, err)
}
sortRowsGoSide(issues, func(i *types.Issue) string { return i.ID }, filter.SortBy, filter.SortDesc)
items, hasMore, err := finishWindow(issues, window)
if err != nil {
return domain.SearchPage{}, err
}
View on GitHub (pinned to 71377f2769)
Solutions
- Read the wrapped scan error to find which column/value failed conversion.
- Run `bd doctor` / schema migrations to bring the DB to the expected schema version.
- Inspect the offending issue row and repair or delete corrupt rows.
- Restore the database from a known-good backup (Dolt history or .beads backup).
Example fix
// before // rows contain NULL priority; scanIssue scans into *int with Scan(&priority) fine, // but a NULL into a plain int fails: issue, scanErr := scanIssue(rows) // "sql: Scan error on column 'priority' // after // migrate/repair the row so values match the schema, or coalesce at read time: SELECT COALESCE(priority, 0) AS priority, ... FROM issues
Defensive patterns
Strategy: validation
Validate before calling
// validate schema version before searching
if err := bd.CheckSchemaVersion(dbPath); err != nil {
return fmt.Errorf("run `bd migrate` first: %w", err)
} Type guard
func isScanError(err error) bool {
return err != nil && strings.Contains(err.Error(), ": scan: ")
} Try / catch
page, err := store.Search(ctx, q, filter)
if err != nil && isScanError(err) {
// schema drift or corrupt row; surface repair guidance to the user
return fmt.Errorf("%w (run `bd doctor` / `bd migrate`)", err)
} Prevention
- Always run migrations when upgrading bd versions; never skip versioned upgrades.
- Do not hand-edit database rows with external SQL tools.
- Back up .beads before bulk external modifications.
When it happens
Trigger: A search hits rows whose stored data violates the expected column shape: NULL in a column scanIssue scans into a value type, text in an integer column, or a schema where issueSelectColumns count/order no longer matches the physical table (schema drift between bd versions).
Common situations: Hand-edited or externally modified Dolt/SQLite rows introducing NULLs; upgrading bd across a schema change without running migrations; partial migration leaving old columns in place.
Related errors
- ErrScan
- %s: %w
- failed to scan federation peer: %w
- db: ChildCounterSQLRepository.NextChildID: scan: %w
- db: CommentSQLRepository.CountsByIssueIDs: scan: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/16a324e960ea4ea8.
Report an issue: GitHub.