gastownhall/beads · error
db: CommentSQLRepository.ListByIssueIDs: rows: %w
Error message
db: CommentSQLRepository.ListByIssueIDs: rows: %w
What it means
ListByIssueIDs calls rows.Err() after the scan loop to detect errors during row streaming; this wrapper surfaces it. The entire per-issue comment grouping is discarded on failure, and IterByIssueID propagates it directly. Callers get no partial data.
Source
Thrown at internal/storage/domain/db/comment.go:103
WHERE issue_id IN (%s)
ORDER BY issue_id, created_at ASC, id ASC
`, table, strings.Join(placeholders, ","))
rows, err := r.runner.QueryContext(ctx, q, args...)
if err != nil {
return nil, fmt.Errorf("db: CommentSQLRepository.ListByIssueIDs: %w", err)
}
defer rows.Close()
for rows.Next() {
var c types.Comment
if err := rows.Scan(&c.ID, &c.IssueID, &c.Author, &c.Text, &c.CreatedAt); err != nil {
return nil, fmt.Errorf("db: CommentSQLRepository.ListByIssueIDs: scan: %w", err)
}
cc := c
result[c.IssueID] = append(result[c.IssueID], &cc)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("db: CommentSQLRepository.ListByIssueIDs: rows: %w", err)
}
return result, nil
}
func (r *commentSQLRepositoryImpl) IterByIssueID(ctx context.Context, issueID string, opts domain.CommentOpts) (storage.Iter[types.Comment], error) {
bulk, err := r.ListByIssueIDs(ctx, []string{issueID}, opts)
if err != nil {
return nil, err
}
return storage.NewSliceIter(bulk[issueID]), nil
}
func (r *commentSQLRepositoryImpl) Insert(ctx context.Context, issueID, author, text string, opts domain.CommentOpts) (*types.Comment, error) {
// Live add: advance past the issue's newest comment so a burst inside one
// second still reads back in write order (issueops.NextLiveCommentTime).
// InsertRecord honors a supplied CreatedAt verbatim, which is what keeps
// imported comments on their original timestamps.
stamp, err := issueops.NextLiveCommentTime(ctx, r.runner, pickCommentTable(opts.UseWispsTable), issueID, time.Now())View on GitHub (pinned to 71377f2769)
Solutions
- Check errors.Is(err, context.DeadlineExceeded) and raise the timeout for large fetches.
- Retry; the operation is read-only and safe to repeat.
- Reduce result size with pagination/chunking of issue IDs.
- Verify connection pool MaxLifetime is shorter than the server's idle timeout.
Example fix
// before: streaming a huge comment set inside a short request context
bulk, err := repo.ListByIssueIDs(ctx, ids, opts)
// after: bounded retry on iteration failure
for attempt := 0; attempt < 3; attempt++ {
bulk, err = repo.ListByIssueIDs(ctx, ids, opts)
if err == nil || !errors.Is(err, context.DeadlineExceeded) { break }
time.Sleep(time.Second * time.Duration(attempt+1))
} Defensive patterns
Strategy: retry
Validate before calling
if err := ctx.Err(); err != nil { return fmt.Errorf("pre-list context check: %w", err) } Type guard
func isListRowsError(err error) bool {
return err != nil && strings.Contains(err.Error(), "ListByIssueIDs: rows:")
} Try / catch
bulk, err := repo.ListByIssueIDs(ctx, ids, opts)
for attempt := 1; attempt <= 3 && isListRowsError(err); attempt++ {
time.Sleep(time.Duration(attempt) * time.Second)
bulk, err = repo.ListByIssueIDs(ctx, ids, opts)
} Prevention
- Use longer deadlines for comment-heavy fetches
- Prefer chunked reads over monolithic queries
- Watch server max_execution_time settings
When it happens
Trigger: Calling ListByIssueIDs/IterByIssueID when the connection drops, the context is canceled, or the server aborts the query while comment rows are being streamed.
Common situations: Request-scoped deadlines expiring on large comment fetches; remote Dolt server over an unstable network; server-side query kill due to max_execution_time.
Related errors
- db: ChildCounterSQLRepository.NextChildID: rows: %w
- db: CommentSQLRepository.CountsByIssueIDs: rows: %w
- get dependencies: rows from %s: %w
- get dependents: rows from %s: %w
- failed to begin transaction: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/1179e86b772b3ad1.
Report an issue: GitHub.