gastownhall/beads · error

db: CommentSQLRepository.CountsByIssueIDs: rows: %w

Error message

db: CommentSQLRepository.CountsByIssueIDs: rows: %w

What it means

CountsByIssueIDs checks rows.Err() after the scan loop; this error wraps any error encountered while streaming rows (network drop, query canceled, server-side abort). The partial counts map is discarded and a nil map with the error is returned, so callers must not trust partial results.

Source

Thrown at internal/storage/domain/db/comment.go:64

		"SELECT issue_id, COUNT(*) FROM %s WHERE issue_id IN (%s) GROUP BY issue_id",
		table, strings.Join(placeholders, ","),
	)
	rows, err := r.runner.QueryContext(ctx, q, args...)
	if err != nil {
		return nil, fmt.Errorf("db: CommentSQLRepository.CountsByIssueIDs: %w", err)
	}
	defer rows.Close()

	for rows.Next() {
		var issueID string
		var count int
		if err := rows.Scan(&issueID, &count); err != nil {
			return nil, fmt.Errorf("db: CommentSQLRepository.CountsByIssueIDs: scan: %w", err)
		}
		result[issueID] = count
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("db: CommentSQLRepository.CountsByIssueIDs: rows: %w", err)
	}
	return result, nil
}

func (r *commentSQLRepositoryImpl) ListByIssueIDs(ctx context.Context, issueIDs []string, opts domain.CommentOpts) (map[string][]*types.Comment, error) {
	result := make(map[string][]*types.Comment)
	if len(issueIDs) == 0 {
		return result, nil
	}
	placeholders := make([]string, len(issueIDs))
	args := make([]any, len(issueIDs))
	for i, id := range issueIDs {
		placeholders[i] = "?"
		args[i] = id
	}
	table := pickCommentTable(opts.UseWispsTable)
	//nolint:gosec // G201: table is one of two hardcoded constants
	q := fmt.Sprintf(`

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped error: context.Canceled/DeadlineExceeded means raise the timeout or fix the cancelation source.
  2. Retry the query; it is read-only and idempotent.
  3. Check network stability and connection pool health (max lifetime vs server wait_timeout).
  4. For large ID lists, chunk the IN clause to shorten query duration.

Example fix

// before: request context expires mid-query
ctx := r.Context() // request-scoped, may be too short

// after: detached context with explicit budget for batch reads
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
Defensive patterns

Strategy: retry

Validate before calling

if err := ctx.Err(); err != nil { return err } // bail early on dead context

Type guard

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

Try / catch

counts, err := repo.CountsByIssueIDs(ctx, ids, opts)
for retry := 0; err != nil && isCountRowsError(err) && retry < 3; retry++ {
    time.Sleep(backoff(retry))
    counts, err = repo.CountsByIssueIDs(ctx, ids, opts)
}

Prevention

When it happens

Trigger: Calling CountsByIssueIDs when the connection drops or the context is canceled while the grouped-count result set is being streamed.

Common situations: HTTP request deadlines canceling mid-query; flaky network to a remote Dolt server; server kill of long-running queries.

Related errors


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