{"record":{"id":"20afc27430341f4a","repo":"gastownhall/beads","slug":"db-commentsqlrepository-countsbyissueids-rows","errorCode":null,"errorMessage":"db: CommentSQLRepository.CountsByIssueIDs: rows: %w","messagePattern":"db: CommentSQLRepository\\.CountsByIssueIDs: rows: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/comment.go","lineNumber":64,"sourceCode":"\t\t\"SELECT issue_id, COUNT(*) FROM %s WHERE issue_id IN (%s) GROUP BY issue_id\",\n\t\ttable, strings.Join(placeholders, \",\"),\n\t)\n\trows, err := r.runner.QueryContext(ctx, q, args...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"db: CommentSQLRepository.CountsByIssueIDs: %w\", err)\n\t}\n\tdefer rows.Close()\n\n\tfor rows.Next() {\n\t\tvar issueID string\n\t\tvar count int\n\t\tif err := rows.Scan(&issueID, &count); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"db: CommentSQLRepository.CountsByIssueIDs: scan: %w\", err)\n\t\t}\n\t\tresult[issueID] = count\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"db: CommentSQLRepository.CountsByIssueIDs: rows: %w\", err)\n\t}\n\treturn result, nil\n}\n\nfunc (r *commentSQLRepositoryImpl) ListByIssueIDs(ctx context.Context, issueIDs []string, opts domain.CommentOpts) (map[string][]*types.Comment, error) {\n\tresult := make(map[string][]*types.Comment)\n\tif len(issueIDs) == 0 {\n\t\treturn result, nil\n\t}\n\tplaceholders := make([]string, len(issueIDs))\n\targs := make([]any, len(issueIDs))\n\tfor i, id := range issueIDs {\n\t\tplaceholders[i] = \"?\"\n\t\targs[i] = id\n\t}\n\ttable := pickCommentTable(opts.UseWispsTable)\n\t//nolint:gosec // G201: table is one of two hardcoded constants\n\tq := fmt.Sprintf(`","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/comment.go#L46-L82","documentation":"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.","triggerScenarios":"Calling CountsByIssueIDs when the connection drops or the context is canceled while the grouped-count result set is being streamed.","commonSituations":"HTTP request deadlines canceling mid-query; flaky network to a remote Dolt server; server kill of long-running queries.","solutions":["Inspect the wrapped error: context.Canceled/DeadlineExceeded means raise the timeout or fix the cancelation source.","Retry the query; it is read-only and idempotent.","Check network stability and connection pool health (max lifetime vs server wait_timeout).","For large ID lists, chunk the IN clause to shorten query duration."],"exampleFix":"// before: request context expires mid-query\nctx := r.Context() // request-scoped, may be too short\n\n// after: detached context with explicit budget for batch reads\nctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\ndefer cancel()","handlingStrategy":"retry","validationCode":"if err := ctx.Err(); err != nil { return err } // bail early on dead context","typeGuard":"func isCountRowsError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"CountsByIssueIDs: rows:\")\n}","tryCatchPattern":"counts, err := repo.CountsByIssueIDs(ctx, ids, opts)\nfor retry := 0; err != nil && isCountRowsError(err) && retry < 3; retry++ {\n    time.Sleep(backoff(retry))\n    counts, err = repo.CountsByIssueIDs(ctx, ids, opts)\n}","preventionTips":["Set explicit deadlines larger than expected query time","Chunk large ID lists to shorten queries","Keep connection MaxLifetime under server wait_timeout"],"tags":["database","sql","rows-iteration","go"],"backgroundTag":"query-iteration-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}