{"record":{"id":"4c16208677e26499","repo":"gastownhall/beads","slug":"db-commentsqlrepository-countsbyissueids-scan","errorCode":null,"errorMessage":"db: CommentSQLRepository.CountsByIssueIDs: scan: %w","messagePattern":"db: CommentSQLRepository\\.CountsByIssueIDs: scan: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/comment.go","lineNumber":59,"sourceCode":"\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\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] = \"?\"","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/comment.go#L41-L77","documentation":"During CountsByIssueIDs, each row (issue_id, COUNT(*)) is scanned into a string and an int. This error wraps a rows.Scan failure decoding one of those columns. It means the result row's column types could not be converted to (string, int) — usually a NULL column or driver type coercion issue.","triggerScenarios":"Calling CountsByIssueIDs when the comment table's `issue_id` column contains NULL, or the driver returns COUNT(*) in a type the driver refuses to convert into Go int (driver version change).","commonSituations":"Schema drift after manual edits or imports that inserted NULL issue_id rows; switching drivers (e.g. between Dolt and MySQL drivers) with different Scan strictness.","solutions":["Find and repair rows where comments.issue_id IS NULL (issue_id should be NOT NULL).","Check the wrapped driver error to identify which column failed and its actual type.","Align the driver version with what beads was built against.","Retry if the error surfaced from a transient conversion edge case after a driver upgrade."],"exampleFix":"// before: NULL issue_id breaks Scan into string\nvar issueID string\nrows.Scan(&issueID, &count)\n\n// after: make column NOT NULL in schema, or scan NullString defensively\nvar issueID sql.NullString\nif err := rows.Scan(&issueID, &count); err != nil { return nil, err }\nif issueID.Valid { result[issueID.String] = count }","handlingStrategy":"try-catch","validationCode":"// ensure no NULL issue_id rows exist before bulk count\nvar bad int\nconn.QueryRowContext(ctx, \"SELECT COUNT(*) FROM comments WHERE issue_id IS NULL\").Scan(&bad)\nif bad > 0 { return fmt.Errorf(\"%d comment rows with NULL issue_id; repair first\", bad) }","typeGuard":"func isCountScanError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"CountsByIssueIDs: scan:\")\n}","tryCatchPattern":"counts, err := repo.CountsByIssueIDs(ctx, ids, opts)\nif err != nil && isCountScanError(err) {\n    // repair offending rows, then retry once\n    repairNullIssueIDs(ctx, conn)\n    counts, err = repo.CountsByIssueIDs(ctx, ids, opts)\n}","preventionTips":["Enforce NOT NULL on comments.issue_id","Validate imported data before insertion","Test Scan behavior after driver upgrades"],"tags":["database","sql","scan","go"],"backgroundTag":"sql-row-scan-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}