gastownhall/beads · error

search %s (id scan): %w

Error message

search %s (id scan): %w

What it means

Raised when the ID-scan phase of searchTable (scanFilterIDs) fails to execute its query. This lighter query selects only table.id with the same FROM/WHERE/ORDER/window clauses; execution errors from the driver are wrapped as "search <table> (id scan): <err>". The ID-scan path is used for a subset of filters, so this error appears only on those code paths.

Source

Thrown at internal/storage/domain/db/issue_search.go:304

	}

	if err := r.hydrateIssues(ctx, items, tables, filter.IncludeDependencies, filter.SkipLabels); err != nil {
		return domain.SearchPage{}, fmt.Errorf("search %s: hydrate: %w", tables.Main, err)
	}

	return domain.SearchPage{Items: items, HasMore: hasMore}, nil
}

func (r *issueSQLRepositoryImpl) scanFilterIDs(ctx context.Context, selectKw, fromSQL, whereSQL string, args []any, filter types.IssueFilter, tables filterTables) ([]string, bool, error) {
	orderBy := orderBySQL(filter.SortBy, filter.SortDesc, tables.Main)
	window := searchWindowForFilter(filter)
	//nolint:gosec // G201: SQL fragments from fixed table names and parameterized filters.
	idQuery := fmt.Sprintf(`%s%s.id FROM %s %s %s %s`,
		selectKw, tables.Main, fromSQL, whereSQL, orderBy, window.sql)

	rows, err := r.runner.QueryContext(ctx, idQuery, args...)
	if err != nil {
		return nil, false, fmt.Errorf("search %s (id scan): %w", tables.Main, err)
	}
	defer func() { _ = rows.Close() }()

	var ids []string
	for rows.Next() {
		var id string
		if err := rows.Scan(&id); err != nil {
			return nil, false, fmt.Errorf("search %s (id scan): scan: %w", tables.Main, err)
		}
		ids = append(ids, id)
	}
	if err := rows.Err(); err != nil {
		return nil, false, fmt.Errorf("search %s (id scan): rows: %w", tables.Main, err)
	}

	sortRowsGoSide(ids, func(id string) string { return id }, filter.SortBy, filter.SortDesc)
	return finishWindow(ids, window)
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Unwrap the error chain to identify the driver-level cause (no such table, locked, connection).
  2. Run `bd doctor` to verify database integrity and schema; re-migrate if needed.
  3. Release concurrent locks (close other bd sessions) and retry.
  4. Extend the context deadline or add filters to shrink the scanned table.

Example fix

// before
ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) // too short for id scan
// after
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := os.Stat(dbPath); err != nil { return err }
if err := ctx.Err(); err != nil { return err }

Type guard

func isIDScanError(err error) bool {
	return err != nil && strings.Contains(err.Error(), "(id scan)")
}

Try / catch

page, err := store.Search(ctx, q, filter)
if err != nil && isIDScanError(err) {
	if dberrors.IsMissingTable(err) {
		return fmt.Errorf("database out of date, run `bd migrate`: %w", err)
	}
	// locked or transient: retry once
}

Prevention

When it happens

Trigger: Calling a search path that routes through scanFilterIDs when the main query against the issues/wisps table fails: missing table, locked DB, connection failure, or cancelled context — same class as the full-search query failure but from the id-only SQL.

Common situations: Database schema drift (table absent); DB file locked by another process; context deadline shorter than the query on large tables; remote Dolt server unreachable.

Related errors


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