gastownhall/beads · error
get labels: scan: %w
Error message
get labels: scan: %w
What it means
This error wraps a rows.Scan failure while reading a label string in GetLabelsInTx. Scan fails when the column value cannot be converted into the destination — typically a NULL label or unexpected type. The library throws it to distinguish row-decoding problems from query-execution problems.
Source
Thrown at internal/storage/issueops/labels.go:30
// Automatically routes to wisp_labels if the ID is an active wisp.
// Returns labels sorted alphabetically.
func GetLabelsInTx(ctx context.Context, tx DBTX, table, issueID string) ([]string, error) {
if table == "" {
isWisp := IsActiveWispInTx(ctx, tx, issueID)
_, table, _, _ = WispTableRouting(isWisp)
}
//nolint:gosec // G201: table is from WispTableRouting ("labels" or "wisp_labels")
rows, err := tx.QueryContext(ctx, fmt.Sprintf(`SELECT label FROM %s WHERE issue_id = ? ORDER BY label`, table), issueID)
if err != nil {
return nil, fmt.Errorf("get labels: %w", err)
}
defer rows.Close()
var labels []string
for rows.Next() {
var label string
if err := rows.Scan(&label); err != nil {
return nil, fmt.Errorf("get labels: scan: %w", err)
}
labels = append(labels, label)
}
return labels, rows.Err()
}
// GetLabelsForIssuesInTx fetches labels for multiple issues in a single transaction.
// Routes each ID to labels or wisp_labels based on wisp status.
// Uses a single batched wisp-partition query plus batched IN clauses per label
// table, so the number of round-trips is O(1 + N/queryBatchSize) rather than
// O(N). This matters on remote backends (Dolt) where per-ID round-trips would
// otherwise blow past the context deadline — see GH#3414.
//
// Callers hydrating multiple batches inside one tx may pass a precomputed
// active-wisp set scoped to issueIDs to avoid rebuilding it.
func GetLabelsForIssuesInTx(ctx context.Context, tx DBTX, issueIDs []string, wispSetOpt ...map[string]struct{}) (map[string][]string, error) {
if len(issueIDs) == 0 {
return make(map[string][]string), nilView on GitHub (pinned to 71377f2769)
Solutions
- Clean up rows with NULL label values: DELETE FROM labels WHERE label IS NULL
- Add a NOT NULL constraint to the label column if schema drift allowed NULLs
- Use COALESCE in queries or scan into sql.NullString if tolerant reads are desired
Example fix
// before
var label string
if err := rows.Scan(&label); err != nil {
return nil, fmt.Errorf("get labels: scan: %w", err)
}
// after (data-cleanup path)
// DELETE FROM labels WHERE label IS NULL;
-- or defensively at call site, tolerate NULLs via a custom scan wrapper Defensive patterns
Strategy: validation
Validate before calling
var nulls int
_ = tx.QueryRow(`SELECT COUNT(*) FROM labels WHERE label IS NULL`).Scan(&nulls)
if nulls > 0 { return fmt.Errorf("%d label rows have NULL label values; clean them up", nulls) } Try / catch
labels, err := issueops.GetLabelsInTx(ctx, tx, table, issueID)
if err != nil {
var scanErr *fmt.ScanError // or inspect message
if strings.Contains(err.Error(), "scan") {
// data corruption path: repair rows before retry
}
return err
} Prevention
- Enforce NOT NULL on label columns
- Block direct writes to storage tables outside the library API
- Validate imported data before inserting label rows
- Add a periodic data-integrity check for NULL aux columns
When it happens
Trigger: A row in labels/wisp_labels whose label column is NULL or holds a non-string type, encountered during iteration in GetLabelsInTx.
Common situations: Manual inserts into the labels table with NULL label values; schema drift where label column type changed; data imported from another tool with malformed rows.
Related errors
- get labels for issues: scan: %w
- db: GetAllConfig: scan: %w
- db: GetCustomTypes: scan custom_types: %w
- ErrScan
- failed to scan dependency keys: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/167ca681dc9c0900.
Report an issue: GitHub.