gastownhall/beads · error

scan comment: %w

Error message

scan comment: %w

What it means

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).

Source

Thrown at internal/storage/issueops/bulk_ops.go:159

		placeholders, args := buildSQLInClause(batch)

		query := fmt.Sprintf(`
			SELECT id, issue_id, author, text, created_at
			FROM %s
			WHERE issue_id IN (%s)
			ORDER BY issue_id, created_at ASC, id ASC
		`, table, placeholders)

		rows, err := tx.QueryContext(ctx, query, args...)
		if err != nil {
			return fmt.Errorf("get comments from %s: %w", table, err)
		}

		for rows.Next() {
			var c types.Comment
			if err := rows.Scan(&c.ID, &c.IssueID, &c.Author, &c.Text, &c.CreatedAt); err != nil {
				_ = rows.Close()
				return fmt.Errorf("scan comment: %w", err)
			}
			result[c.IssueID] = append(result[c.IssueID], &c)
		}
		if err := rows.Err(); err != nil {
			_ = rows.Close()
			return err
		}
		_ = rows.Close()
	}
	return nil
}

// DeleteIssuesBySourceRepoInTx removes all issues from a source repo and their related data.
//
//nolint:gosec // G201: table is validated by hardcoded list
func DeleteIssuesBySourceRepoInTx(ctx context.Context, tx *sql.Tx, sourceRepo string) (int, error) {
	rows, err := tx.QueryContext(ctx, `SELECT id FROM issues WHERE source_repo = ?`, sourceRepo)
	if err != nil {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Find the wrapped scan error to see which column failed (e.g. "converting NULL to string")
  2. Fix the offending row data (replace NULLs with defaults) or make the target field a pointer/nullable type
  3. Re-run migrations so the schema matches the SELECT column list
  4. If schema is intentionally different, update the SELECT and Scan targets together

Example fix

// before: NULL created_at panics the scan
var c types.Comment
rows.Scan(&c.ID, &c.IssueID, &c.Author, &c.Text, &c.CreatedAt)
// after: tolerate NULLs
var createdAt sql.NullTime
rows.Scan(&c.ID, &c.IssueID, &c.Author, &c.Text, &createdAt)
if createdAt.Valid { c.CreatedAt = createdAt.Time }
Defensive patterns

Strategy: validation

Validate before calling

// sanity-check row data before bulk reads if you can query directly
rows, _ := db.Query("SELECT COUNT(*) FROM comments WHERE created_at IS NULL OR author IS NULL")

Type guard

// narrow the scan failure
var convErr *sql.ConvertError
if errors.As(err, &convErr) { /* log column convErr.Source/Value */ }

Try / catch

if err := GetCommentsForIssuesInTx(ctx, tx, ids); err != nil {
    if strings.Contains(err.Error(), "converting NULL") {
        // repair NULL columns or upgrade to a nullable schema
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/a4669d11de52e798. Report an issue: GitHub.