{"record":{"id":"0eea5e310c37f85e","repo":"gastownhall/beads","slug":"db-commentsqlrepository-listbyissueids-w","errorCode":null,"errorMessage":"db: CommentSQLRepository.ListByIssueIDs: %w","messagePattern":"db: CommentSQLRepository\\.ListByIssueIDs: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/comment.go","lineNumber":90,"sourceCode":"\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(`\n\t\tSELECT id, issue_id, author, text, created_at\n\t\tFROM %s\n\t\tWHERE issue_id IN (%s)\n\t\tORDER BY issue_id, created_at ASC, id ASC\n\t`, table, strings.Join(placeholders, \",\"))\n\trows, err := r.runner.QueryContext(ctx, q, args...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"db: CommentSQLRepository.ListByIssueIDs: %w\", err)\n\t}\n\tdefer rows.Close()\n\n\tfor rows.Next() {\n\t\tvar c types.Comment\n\t\tif err := rows.Scan(&c.ID, &c.IssueID, &c.Author, &c.Text, &c.CreatedAt); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"db: CommentSQLRepository.ListByIssueIDs: scan: %w\", err)\n\t\t}\n\t\tcc := c\n\t\tresult[c.IssueID] = append(result[c.IssueID], &cc)\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"db: CommentSQLRepository.ListByIssueIDs: rows: %w\", err)\n\t}\n\treturn result, nil\n}\n\nfunc (r *commentSQLRepositoryImpl) IterByIssueID(ctx context.Context, issueID string, opts domain.CommentOpts) (storage.Iter[types.Comment], error) {","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/comment.go#L72-L108","documentation":"ListByIssueIDs issues a SELECT of id, issue_id, author, text, created_at from `comments`/`wisp_comments` for the given issue IDs, ordered for deterministic grouping. This error wraps the QueryContext call failing before any rows are produced — the query itself was rejected (missing table, bad connection, canceled context). It is also propagated to callers of IterByIssueID.","triggerScenarios":"Calling ListByIssueIDs (or IterByIssueID) with a huge issueIDs list overflowing placeholders; querying `wisp_comments` when the wisp schema is absent; canceled context; broken connection.","commonSituations":"Bulk dashboards passing thousands of issue IDs at once; databases created before wisp tables were introduced; connection pool exhaustion under load.","solutions":["Chunk large issueIDs slices (e.g. 500–1000 per query) to avoid IN-clause/placeholder limits.","Ensure migrations created the selected table; match UseWispsTable to the schema you actually have.","Inspect the wrapped driver error for connectivity vs schema causes (dberrors.IsTableNotExist).","Retry on transient connection errors."],"exampleFix":"// before: one query with unbounded ID list\ncomments, err := repo.ListByIssueIDs(ctx, allThousandIDs, opts)\n\n// after: chunk the ID list\nfor chunk := range slices.Chunk(allThousandIDs, 500) {\n    batch, err := repo.ListByIssueIDs(ctx, chunk, opts)\n    if err != nil { return err }\n    // merge batch\n}","handlingStrategy":"validation","validationCode":"const maxInClause = 500\nif len(issueIDs) > maxInClause {\n    return fmt.Errorf(\"split %d issue IDs into chunks of %d\", len(issueIDs), maxInClause)\n}","typeGuard":"func isListQueryError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"ListByIssueIDs:\") &&\n        !strings.Contains(err.Error(), \"scan:\") && !strings.Contains(err.Error(), \"rows:\")\n}","tryCatchPattern":"comments, err := repo.ListByIssueIDs(ctx, ids, opts)\nif err != nil && isListQueryError(err) {\n    if dberrors.IsTableNotExist(err) { migrate(); comments, err = repo.ListByIssueIDs(ctx, ids, opts) }\n    if err != nil { return nil, err }\n}","preventionTips":["Chunk IN lists to a few hundred IDs","Migrate wisp tables before using UseWispsTable","Handle IterByIssueID as a direct passthrough of this error"],"tags":["database","sql","query","go"],"backgroundTag":"table-not-exist","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}