gastownhall/beads · error

get issues by IDs: rows: %w

Error message

get issues by IDs: rows: %w

What it means

Wraps the rows.Err() check after iterating the ID-batch result set in GetIssuesByIDsInTx. It indicates the row iteration itself failed (driver/connection error mid-stream) rather than a scan problem.

Source

Thrown at internal/storage/issueops/dependencies.go:1062

			rows, err := tx.QueryContext(ctx, fmt.Sprintf(
				`SELECT %s FROM %s %s WHERE id IN (%s)`,
				IssueSelectColumns, pair.table, sqlbuild.LeaseJoin(pair.table), inClause), args...)
			if err != nil {
				return nil, fmt.Errorf("get issues by IDs from %s: %w", pair.table, err)
			}
			issueMap := make(map[string]*types.Issue)
			for rows.Next() {
				issue, scanErr := ScanIssueFrom(rows)
				if scanErr != nil {
					_ = rows.Close()
					return nil, fmt.Errorf("get issues by IDs: scan: %w", scanErr)
				}
				allIssues = append(allIssues, issue)
				issueMap[issue.ID] = issue
			}
			_ = rows.Close()
			if err := rows.Err(); err != nil {
				return nil, fmt.Errorf("get issues by IDs: rows: %w", err)
			}

			// Hydrate labels.
			if len(issueMap) > 0 {
				labelRows, err := tx.QueryContext(ctx, fmt.Sprintf(
					`SELECT issue_id, label FROM %s WHERE issue_id IN (%s) ORDER BY issue_id, label`,
					pair.labelTbl, inClause), args...)
				if err != nil {
					return nil, fmt.Errorf("get issues by IDs: labels from %s: %w", pair.labelTbl, err)
				}
				for labelRows.Next() {
					var issueID, label string
					if scanErr := labelRows.Scan(&issueID, &label); scanErr != nil {
						_ = labelRows.Close()
						return nil, fmt.Errorf("get issues by IDs: scan label: %w", scanErr)
					}
					if issue, ok := issueMap[issueID]; ok {
						issue.Labels = append(issue.Labels, label)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry the operation if the cause is a transient connection error
  2. Check for context cancellation upstream and raise the caller's timeout if too short
  3. Investigate network stability / DB proxy idle timeouts for large result sets
  4. Keep transactions short so they don't outlive connection lifetimes
Defensive patterns

Strategy: retry

Validate before calling

// Ensure the context has adequate deadline before long batch fetches:
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
// Ensure connection pool health before large reads:
if err := db.PingContext(ctx); err != nil { return err }

Try / catch

issues, err := GetIssuesByIDsInTx(ctx, tx, ids, nil)
if err != nil && strings.Contains(err.Error(), "get issues by IDs: rows") {
    if errors.Is(err, context.Canceled) {
        return fmt.Errorf("caller canceled; raise timeout: %w", err)
    }
    if isTransientDBError(err) {
        issues, err = GetIssuesByIDsInTx(ctx, tx, ids, nil) // retry once
    }
}

Prevention

When it happens

Trigger: After consuming all rows from the `WHERE id IN (...)` SELECT, rows.Err() is non-nil — typically a dropped connection, context cancellation, or driver-level streaming failure during iteration.

Common situations: Long-running iteration over a large result set while the connection dies; request context canceled (client timeout) mid-query; network instability to the database.

Related errors


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