{"record":{"id":"167ca681dc9c0900","repo":"gastownhall/beads","slug":"get-labels-scan-w-167ca6","errorCode":null,"errorMessage":"get labels: scan: %w","messagePattern":"get labels: scan: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/labels.go","lineNumber":30,"sourceCode":"// Automatically routes to wisp_labels if the ID is an active wisp.\n// Returns labels sorted alphabetically.\nfunc GetLabelsInTx(ctx context.Context, tx DBTX, table, issueID string) ([]string, error) {\n\tif table == \"\" {\n\t\tisWisp := IsActiveWispInTx(ctx, tx, issueID)\n\t\t_, table, _, _ = WispTableRouting(isWisp)\n\t}\n\t//nolint:gosec // G201: table is from WispTableRouting (\"labels\" or \"wisp_labels\")\n\trows, err := tx.QueryContext(ctx, fmt.Sprintf(`SELECT label FROM %s WHERE issue_id = ? ORDER BY label`, table), issueID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"get labels: %w\", err)\n\t}\n\tdefer rows.Close()\n\n\tvar labels []string\n\tfor rows.Next() {\n\t\tvar label string\n\t\tif err := rows.Scan(&label); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"get labels: scan: %w\", err)\n\t\t}\n\t\tlabels = append(labels, label)\n\t}\n\treturn labels, rows.Err()\n}\n\n// GetLabelsForIssuesInTx fetches labels for multiple issues in a single transaction.\n// Routes each ID to labels or wisp_labels based on wisp status.\n// Uses a single batched wisp-partition query plus batched IN clauses per label\n// table, so the number of round-trips is O(1 + N/queryBatchSize) rather than\n// O(N). This matters on remote backends (Dolt) where per-ID round-trips would\n// otherwise blow past the context deadline — see GH#3414.\n//\n// Callers hydrating multiple batches inside one tx may pass a precomputed\n// active-wisp set scoped to issueIDs to avoid rebuilding it.\nfunc GetLabelsForIssuesInTx(ctx context.Context, tx DBTX, issueIDs []string, wispSetOpt ...map[string]struct{}) (map[string][]string, error) {\n\tif len(issueIDs) == 0 {\n\t\treturn make(map[string][]string), nil","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/labels.go#L12-L48","documentation":"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.","triggerScenarios":"A row in labels/wisp_labels whose label column is NULL or holds a non-string type, encountered during iteration in GetLabelsInTx.","commonSituations":"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.","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"],"exampleFix":"// before\nvar label string\nif err := rows.Scan(&label); err != nil {\n    return nil, fmt.Errorf(\"get labels: scan: %w\", err)\n}\n// after (data-cleanup path)\n// DELETE FROM labels WHERE label IS NULL;\n-- or defensively at call site, tolerate NULLs via a custom scan wrapper","handlingStrategy":"validation","validationCode":"var nulls int\n_ = tx.QueryRow(`SELECT COUNT(*) FROM labels WHERE label IS NULL`).Scan(&nulls)\nif nulls > 0 { return fmt.Errorf(\"%d label rows have NULL label values; clean them up\", nulls) }","typeGuard":null,"tryCatchPattern":"labels, err := issueops.GetLabelsInTx(ctx, tx, table, issueID)\nif err != nil {\n    var scanErr *fmt.ScanError // or inspect message\n    if strings.Contains(err.Error(), \"scan\") {\n        // data corruption path: repair rows before retry\n    }\n    return err\n}","preventionTips":["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"],"tags":["database","scan","labels","null-value"],"backgroundTag":"sql-scan-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}