gastownhall/beads · error
db: CommentSQLRepository.CountsByIssueIDs: scan: %w
Error message
db: CommentSQLRepository.CountsByIssueIDs: scan: %w
What it means
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.
Source
Thrown at internal/storage/domain/db/comment.go:59
args[i] = id
}
table := pickCommentTable(opts.UseWispsTable)
//nolint:gosec // G201: table is one of two hardcoded constants
q := fmt.Sprintf(
"SELECT issue_id, COUNT(*) FROM %s WHERE issue_id IN (%s) GROUP BY issue_id",
table, strings.Join(placeholders, ","),
)
rows, err := r.runner.QueryContext(ctx, q, args...)
if err != nil {
return nil, fmt.Errorf("db: CommentSQLRepository.CountsByIssueIDs: %w", err)
}
defer rows.Close()
for rows.Next() {
var issueID string
var count int
if err := rows.Scan(&issueID, &count); err != nil {
return nil, fmt.Errorf("db: CommentSQLRepository.CountsByIssueIDs: scan: %w", err)
}
result[issueID] = count
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("db: CommentSQLRepository.CountsByIssueIDs: rows: %w", err)
}
return result, nil
}
func (r *commentSQLRepositoryImpl) ListByIssueIDs(ctx context.Context, issueIDs []string, opts domain.CommentOpts) (map[string][]*types.Comment, error) {
result := make(map[string][]*types.Comment)
if len(issueIDs) == 0 {
return result, nil
}
placeholders := make([]string, len(issueIDs))
args := make([]any, len(issueIDs))
for i, id := range issueIDs {
placeholders[i] = "?"View on GitHub (pinned to 71377f2769)
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.
Example fix
// before: NULL issue_id breaks Scan into string
var issueID string
rows.Scan(&issueID, &count)
// after: make column NOT NULL in schema, or scan NullString defensively
var issueID sql.NullString
if err := rows.Scan(&issueID, &count); err != nil { return nil, err }
if issueID.Valid { result[issueID.String] = count } Defensive patterns
Strategy: try-catch
Validate before calling
// ensure no NULL issue_id rows exist before bulk count
var bad int
conn.QueryRowContext(ctx, "SELECT COUNT(*) FROM comments WHERE issue_id IS NULL").Scan(&bad)
if bad > 0 { return fmt.Errorf("%d comment rows with NULL issue_id; repair first", bad) } Type guard
func isCountScanError(err error) bool {
return err != nil && strings.Contains(err.Error(), "CountsByIssueIDs: scan:")
} Try / catch
counts, err := repo.CountsByIssueIDs(ctx, ids, opts)
if err != nil && isCountScanError(err) {
// repair offending rows, then retry once
repairNullIssueIDs(ctx, conn)
counts, err = repo.CountsByIssueIDs(ctx, ids, opts)
} Prevention
- Enforce NOT NULL on comments.issue_id
- Validate imported data before insertion
- Test Scan behavior after driver upgrades
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- db: ChildCounterSQLRepository.NextChildID: scan: %w
- db: CommentSQLRepository.ListByIssueIDs: scan: %w
- db: LabelSQLRepository.List: scan: %w
- db: LabelSQLRepository.ListByIssueIDs: scan: %w
- get issues by IDs: scan: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/4c16208677e26499.
Report an issue: GitHub.