gastownhall/beads · error

search %s (id scan): rows: %w

Error message

search %s (id scan): rows: %w

What it means

Raised when rows.Err() is non-nil after iterating all id rows in scanFilterIDs. As with the full-search variant, this catches errors surfaced during row streaming (connection drop, driver I/O) rather than at query execution.

Source

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

	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)
}

func (r *issueSQLRepositoryImpl) hydrateIssues(ctx context.Context, issues []*types.Issue, tables filterTables, includeDeps bool, skipLabels bool) error {
	if len(issues) == 0 {
		return nil
	}

	ids := make([]string, len(issues))
	for i, issue := range issues {
		ids[i] = issue.ID
	}

	if !skipLabels {
		labelMap, err := r.getLabelsFromTable(ctx, tables.Labels, ids)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped error for connection vs I/O cause and retry the search.
  2. Move large workloads close to the database (avoid NAS-hosted .beads directories).
  3. Narrow the filter (status, limit, label) to reduce streamed rows.
  4. Increase driver timeout/read settings for remote Dolt connections.

Example fix

// before
db on NAS path; ids scan drops mid-iteration
// after
move .beads to local disk or run bd against a local dolt server
Defensive patterns

Strategy: retry

Validate before calling

if err := ctx.Err(); err != nil { return err }
// keep DB on local storage; check network before remote dolt server calls
if err := pingDoltServer(ctx); err != nil { return err }

Type guard

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

Try / catch

err := retry.Do(3, backoff, func() error {
	_, e := store.Search(ctx, q, filter)
	if e != nil && isIDScanRowsError(e) {
		return e // transient stream drop
	}
	return retry.Stop(e)
})

Prevention

When it happens

Trigger: Streaming the id list for a filtered search when the connection dies mid-iteration: network loss to a remote Dolt server, DB process termination, or file I/O failure on an embedded database.

Common situations: Very large issue tables scanned over flaky network connections; embedded SQLite on network-attached storage; laptop sleep / network switch during a long scan.

Related errors


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