{"record":{"id":"39049630470b54c2","repo":"gastownhall/beads","slug":"get-labels-for-issues-scan-w","errorCode":null,"errorMessage":"get labels for issues: scan: %w","messagePattern":"get labels for issues: scan: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/labels.go","lineNumber":119,"sourceCode":"\t\t}\n\t\tbatch := ids[start:end]\n\t\tplaceholders := make([]string, len(batch))\n\t\targs := make([]any, len(batch))\n\t\tfor i, id := range batch {\n\t\t\tplaceholders[i] = \"?\"\n\t\t\targs[i] = id\n\t\t}\n\t\trows, err := tx.QueryContext(ctx, fmt.Sprintf(\n\t\t\t`SELECT issue_id, label FROM %s WHERE issue_id IN (%s) ORDER BY issue_id, label`,\n\t\t\tlabelTable, strings.Join(placeholders, \",\")), args...)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"get labels for issues from %s: %w\", labelTable, err)\n\t\t}\n\t\tfor rows.Next() {\n\t\t\tvar issueID, label string\n\t\t\tif err := rows.Scan(&issueID, &label); err != nil {\n\t\t\t\t_ = rows.Close()\n\t\t\t\treturn fmt.Errorf(\"get labels for issues: scan: %w\", err)\n\t\t\t}\n\t\t\tresult[issueID] = append(result[issueID], label)\n\t\t}\n\t\t_ = rows.Close()\n\t\tif err := rows.Err(); err != nil {\n\t\t\treturn fmt.Errorf(\"get labels for issues: rows: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// AddLabelInTx adds a label to an issue and records an event within an existing\n// transaction. Automatically routes to wisp tables if the ID is an active wisp.\n// Uses INSERT IGNORE for idempotency.\nfunc AddLabelInTx(ctx context.Context, tx DBTX, labelTable, eventTable, issueID, label, actor string) error {\n\t// Reject an over-length label up front. The INSERT IGNORE below would\n\t// otherwise silently truncate it to the VARCHAR(255) column, storing a label\n\t// the caller never sent; a typed ErrFieldTooLong is the clean rejection.","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/labels.go#L101-L137","documentation":"This error wraps a rows.Scan failure in getLabelsIntoFromTable while decoding an (issue_id, label) row pair into strings. It indicates a row whose columns are NULL or of unexpected types in the batched label fetch. The library closes rows and aborts the whole batch rather than returning partial label maps.","triggerScenarios":"Rows in labels/wisp_labels with NULL issue_id or label values encountered during GetLabelsForIssuesInTx batch iteration.","commonSituations":"Corrupted or manually inserted rows; schema drift allowing NULLs; partial imports from migrations.","solutions":["Delete or repair rows with NULL issue_id/label: DELETE FROM labels WHERE issue_id IS NULL OR label IS NULL","Add NOT NULL constraints to prevent recurrence","If soft handling is needed, patch the scan to use sql.NullString and skip invalid rows"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"var nulls int\n_ = tx.QueryRow(`SELECT COUNT(*) FROM labels WHERE issue_id IS NULL OR label IS NULL`).Scan(&nulls)\nif nulls > 0 { return fmt.Errorf(\"%d malformed label rows; repair before bulk hydration\", nulls) }","typeGuard":null,"tryCatchPattern":"m, err := issueops.GetLabelsForIssuesInTx(ctx, tx, ids)\nif err != nil {\n    if strings.Contains(err.Error(), \"scan\") {\n        // locate and repair NULL rows, then retry\n    }\n    return err\n}","preventionTips":["Add NOT NULL constraints to issue_id and label columns","Sanitize data imported from external tools","Avoid manual SQL inserts into storage tables","Schedule integrity checks for NULL values in aux tables"],"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"}