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
- Retry the operation if the cause is a transient connection error
- Check for context cancellation upstream and raise the caller's timeout if too short
- Investigate network stability / DB proxy idle timeouts for large result sets
- 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
- Give batch fetches generous context deadlines
- Keep result sets bounded (reasonable ID-list sizes)
- Harden network path to DB (proxy idle timeouts > query time)
- Distinguish context.Canceled from transient DB errors before retrying
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
- failed to begin transaction: %w
- failed to recompute is_blocked: %w
- failed to commit is_blocked repairs: %w
- failed to query orphaned dependencies: %w
- row iteration error: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/03fd6187a24cd05a.
Report an issue: GitHub.