gastownhall/beads · error

db: LabelSQLRepository.ListByIssueIDs: scan: %w

Error message

db: LabelSQLRepository.ListByIssueIDs: scan: %w

What it means

LabelSQLRepository.ListByIssueIDs failed while scanning one (issue_id, label) row into two strings. The query ran but a returned row could not be decoded into the destination types.

Source

Thrown at internal/storage/domain/db/label.go:179

		placeholders[i] = "?"
		args[i] = id
	}
	table := pickLabelTable(opts.UseWispsTable)
	//nolint:gosec // G201: table is one of two hardcoded constants
	q := fmt.Sprintf(
		"SELECT issue_id, label FROM %s WHERE issue_id IN (%s) ORDER BY issue_id, label",
		table, strings.Join(placeholders, ","),
	)
	rows, err := r.runner.QueryContext(ctx, q, args...)
	if err != nil {
		return nil, fmt.Errorf("db: LabelSQLRepository.ListByIssueIDs: %w", err)
	}
	defer rows.Close()

	for rows.Next() {
		var issueID, label string
		if err := rows.Scan(&issueID, &label); err != nil {
			return nil, fmt.Errorf("db: LabelSQLRepository.ListByIssueIDs: scan: %w", err)
		}
		result[issueID] = append(result[issueID], label)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("db: LabelSQLRepository.ListByIssueIDs: rows: %w", err)
	}
	return result, nil
}

func (r *labelSQLRepositoryImpl) DeleteAllForIDs(ctx context.Context, ids []string, opts domain.LabelOpts) (int, error) {
	if len(ids) == 0 {
		return 0, nil
	}
	table := "labels"
	if opts.UseWispsTable {
		table = "wisp_labels"
	}
	total := 0

View on GitHub (pinned to 71377f2769)

Solutions

  1. Unwrap the error to identify which column failed to scan
  2. Check the table schema: issue_id and label must be NOT NULL string/text columns
  3. Migrate or delete NULL/malformed rows in the labels table
  4. If values can be NULL legitimately, scan into sql.NullString and convert after

Example fix

// before
var issueID, label string
if err := rows.Scan(&issueID, &label); err != nil { ... }
// after
var issueID, label sql.NullString
if err := rows.Scan(&issueID, &label); err != nil { ... }
if issueID.Valid && label.Valid { result[issueID.String] = append(...) }
Defensive patterns

Strategy: try-catch

Validate before calling

// check schema nullability before reads
// SELECT is_nullable FROM information_schema.columns WHERE table_name='issue_labels' AND column_name IN ('issue_id','label')

Type guard

func isScanErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "scan: ")
}

Try / catch

m, err := repo.ListByIssueIDs(ctx, ids, opts)
if err != nil {
    if strings.Contains(err.Error(), "scan: ") {
        log.Printf("bad row in labels: %v", errors.Unwrap(err))
    }
    return err
}

Prevention

When it happens

Trigger: Calling ListByIssueIDs when a row has NULL issue_id or NULL label, or the driver yields a column type incompatible with string.

Common situations: Schema drift making issue_id/label nullable; a corrupted or manually edited labels table; custom driver returning non-standard types (e.g. binary issue_id columns).

Related errors


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