{"record":{"id":"3c1bd29d559a651c","repo":"gastownhall/beads","slug":"db-labelsqlrepository-list-rows-w","errorCode":null,"errorMessage":"db: LabelSQLRepository.List: rows: %w","messagePattern":"db: LabelSQLRepository\\.List: rows: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/label.go","lineNumber":148,"sourceCode":"\trows, err := r.runner.QueryContext(ctx,\n\t\tfmt.Sprintf(\"SELECT label FROM %s WHERE issue_id = ? ORDER BY label\", table),\n\t\tissueID,\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"db: LabelSQLRepository.List %s: %w\", issueID, err)\n\t}\n\tdefer rows.Close()\n\n\tvar out []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(\"db: LabelSQLRepository.List: scan: %w\", err)\n\t\t}\n\t\tout = append(out, label)\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"db: LabelSQLRepository.List: rows: %w\", err)\n\t}\n\treturn out, nil\n}\n\nfunc (r *labelSQLRepositoryImpl) ListByIssueIDs(ctx context.Context, issueIDs []string, opts domain.LabelOpts) (map[string][]string, error) {\n\tresult := make(map[string][]string)\n\tif len(issueIDs) == 0 {\n\t\treturn result, nil\n\t}\n\tplaceholders := make([]string, len(issueIDs))\n\targs := make([]any, len(issueIDs))\n\tfor i, id := range issueIDs {\n\t\tplaceholders[i] = \"?\"\n\t\targs[i] = id\n\t}\n\ttable := pickLabelTable(opts.UseWispsTable)\n\t//nolint:gosec // G201: table is one of two hardcoded constants\n\tq := fmt.Sprintf(","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/label.go#L130-L166","documentation":"LabelSQLRepository.List failed at the end of iteration: rows.Err() returned non-nil after all rows were consumed. This indicates the rows stream broke mid-iteration (connection loss, driver error, context cancellation) rather than a per-row conversion failure.","triggerScenarios":"Calling List when the underlying connection drops or the context is cancelled/times out while rows are still being iterated.","commonSituations":"Long-running queries over large label sets hitting context deadlines; network interruption to a remote database; embedded database closed mid-query; driver-level errors surfaced only after iteration completes.","solutions":["Unwrap the error to see the driver-level cause (context deadline, connection reset, etc.)","Check caller-side context timeouts: increase the deadline or pass a non-cancelled context","Verify database connectivity/restart if the connection was dropped","If datasets are large, paginate or narrow the query instead of streaming all rows in one call"],"exampleFix":"// before\nctx := context.Background()\nlabels, _ := repo.List(ctx, opts)\n// after\nctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\ndefer cancel()\nlabels, _ := repo.List(ctx, opts)","handlingStrategy":"try-catch","validationCode":"// ensure context has sane deadline before call\nif _, ok := ctx.Deadline(); !ok {\n    var cancel context.CancelFunc\n    ctx, cancel = context.WithTimeout(ctx, 30*time.Second)\n    defer cancel()\n}","typeGuard":null,"tryCatchPattern":"labels, err := repo.List(ctx, opts)\nif err != nil {\n    var cause error\n    errors.As(err, &cause)\n    if errors.Is(err, context.DeadlineExceeded) || isNetErr(cause) {\n        // retry with backoff\n    }\n    return err\n}","preventionTips":["Always pass a context with an appropriate timeout","Avoid streaming unbounded row sets; add LIMIT/pagination","Monitor connection health for remote databases","Do not close the DB handle while queries are in flight"],"tags":["go","database","sql","iteration"],"backgroundTag":"sql-rows-iteration-error","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}