{"record":{"id":"a4669d11de52e798","repo":"gastownhall/beads","slug":"scan-comment-w","errorCode":null,"errorMessage":"scan comment: %w","messagePattern":"scan comment: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/bulk_ops.go","lineNumber":159,"sourceCode":"\t\tplaceholders, args := buildSQLInClause(batch)\n\n\t\tquery := fmt.Sprintf(`\n\t\t\tSELECT id, issue_id, author, text, created_at\n\t\t\tFROM %s\n\t\t\tWHERE issue_id IN (%s)\n\t\t\tORDER BY issue_id, created_at ASC, id ASC\n\t\t`, table, placeholders)\n\n\t\trows, err := tx.QueryContext(ctx, query, args...)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"get comments from %s: %w\", table, err)\n\t\t}\n\n\t\tfor rows.Next() {\n\t\t\tvar c types.Comment\n\t\t\tif err := rows.Scan(&c.ID, &c.IssueID, &c.Author, &c.Text, &c.CreatedAt); err != nil {\n\t\t\t\t_ = rows.Close()\n\t\t\t\treturn fmt.Errorf(\"scan comment: %w\", err)\n\t\t\t}\n\t\t\tresult[c.IssueID] = append(result[c.IssueID], &c)\n\t\t}\n\t\tif err := rows.Err(); err != nil {\n\t\t\t_ = rows.Close()\n\t\t\treturn err\n\t\t}\n\t\t_ = rows.Close()\n\t}\n\treturn nil\n}\n\n// DeleteIssuesBySourceRepoInTx removes all issues from a source repo and their related data.\n//\n//nolint:gosec // G201: table is validated by hardcoded list\nfunc DeleteIssuesBySourceRepoInTx(ctx context.Context, tx *sql.Tx, sourceRepo string) (int, error) {\n\trows, err := tx.QueryContext(ctx, `SELECT id FROM issues WHERE source_repo = ?`, sourceRepo)\n\tif err != nil {","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/bulk_ops.go#L141-L177","documentation":"After querying comments, each row is scanned into a types.Comment via rows.Scan; any column-count or type mismatch produces \"scan comment: %w\". It indicates the result row shape does not match the five expected columns (id, issue_id, author, text, created_at).","triggerScenarios":"rows.Scan returns an error because a column is NULL into a non-pointer non-nullable field (e.g. NULL author or created_at), the table schema diverged from the SELECT column list, or the driver returns an incompatible type for a scanned field.","commonSituations":"Hand-edited or partially migrated database where comments rows have NULL created_at; custom fork changed the comments schema but not this query; driver type mismatch after a storage backend change.","solutions":["Find the wrapped scan error to see which column failed (e.g. \"converting NULL to string\")","Fix the offending row data (replace NULLs with defaults) or make the target field a pointer/nullable type","Re-run migrations so the schema matches the SELECT column list","If schema is intentionally different, update the SELECT and Scan targets together"],"exampleFix":"// before: NULL created_at panics the scan\nvar c types.Comment\nrows.Scan(&c.ID, &c.IssueID, &c.Author, &c.Text, &c.CreatedAt)\n// after: tolerate NULLs\nvar createdAt sql.NullTime\nrows.Scan(&c.ID, &c.IssueID, &c.Author, &c.Text, &createdAt)\nif createdAt.Valid { c.CreatedAt = createdAt.Time }","handlingStrategy":"validation","validationCode":"// sanity-check row data before bulk reads if you can query directly\nrows, _ := db.Query(\"SELECT COUNT(*) FROM comments WHERE created_at IS NULL OR author IS NULL\")","typeGuard":"// narrow the scan failure\nvar convErr *sql.ConvertError\nif errors.As(err, &convErr) { /* log column convErr.Source/Value */ }","tryCatchPattern":"if err := GetCommentsForIssuesInTx(ctx, tx, ids); err != nil {\n    if strings.Contains(err.Error(), \"converting NULL\") {\n        // repair NULL columns or upgrade to a nullable schema\n    }\n    return err\n}","preventionTips":["Avoid hand-editing database rows; keep NOT NULL columns populated","Re-run migrations after any schema change or version upgrade","Keep the SELECT column list and struct fields in sync when forking"],"tags":["database","sql-scan","schema-mismatch"],"backgroundTag":"sql-scan-error","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}