{"record":{"id":"0590b12a69ec00c7","repo":"gastownhall/beads","slug":"search-count-s-rows-w-0590b1","errorCode":null,"errorMessage":"search count %s: rows: %w","messagePattern":"search count (.+?): rows: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/search_counts.go","lineNumber":180,"sourceCode":"\n\tvar out []*types.IssueWithCounts\n\tseen := make(map[string]bool)\n\tfor rows.Next() {\n\t\tiwc, scanErr := ScanReadyWorkRowWithCounts(rows, hyd)\n\t\tif scanErr != nil {\n\t\t\treturn nil, scanErr\n\t\t}\n\t\tif iwc == nil || iwc.Issue == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif seen[iwc.Issue.ID] {\n\t\t\tcontinue\n\t\t}\n\t\tseen[iwc.Issue.ID] = true\n\t\tout = append(out, iwc)\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"search count %s: rows: %w\", mainTable, err)\n\t}\n\treturn out, nil\n}\n\n// finishSearchIssuesWithCounts is the single terminal hook every\n// SearchIssuesWithCountsInTx exit path routes through: it sorts the merged\n// result, applies the caller-facing Limit trim, and only then enforces the\n// defensive MaxRows cap (be-x42v) on the delivered count — mirroring\n// searchInTx's trimToSearchLimit-before-EnforceMaxRowsCap ordering and\n// finishReadyWorkWithCounts in ready_work_counts.go.\n//\n// Trim-before-cap matters for the merged (issues+wisps) case:\n// runFilterSearchQueryInTx sizes each leg's SQL LIMIT independently via\n// EffectiveSearchLimit(filter.Limit, filter.MaxRows), so the merged\n// pre-trim slice can hold up to ~2x that per-leg bound — e.g. Limit=2,\n// MaxRows=5, 3 rows in each table merges to 6, which would trip MaxRows\n// even though the page actually handed back to the caller (trimmed to\n// Limit=2) is well within the cap. Checking the cap against the delivered","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/search_counts.go#L162-L198","documentation":"scanCountsRowsInTx wraps the error from rows.Err() after iterating the counts result set. The counts query started fine and rows scanned (or were skipped as duplicate IDs), but the driver reported a failure at end-of-iteration — typically a mid-stream connection or server failure only visible once the result set is exhausted.","triggerScenarios":"During iteration of the count-projection SELECT in runReadyCountsInTx/runSearchQueryInTx, the connection breaks, the server kills the query, or ctx is cancelled between Next() calls; the deferred error is surfaced via rows.Err() and wrapped with the main table name.","commonSituations":"Long-running count queries over large corpora hitting a server-side timeout; network blip to a remote Dolt server; SQLite interrupted by a process signal mid-scan.","solutions":["Unwrap the rows error and retry if transient (reset connection, killed query)","Raise the context timeout for large count scans","Narrow the filter (status, assignee, limit) to shrink the scanned set","Check server-side kill/timeout logs and raise them if they cut the query","Ensure the process is not receiving signals/interrupts mid-scan (e.g. CI cancellation)"],"exampleFix":"// before: default deadline too short for a full-repo count scan\nctx, cancel := context.WithTimeout(ctx, 2*time.Second)\n// after: size the deadline to the corpus\nctx, cancel := context.WithTimeout(ctx, 60*time.Second)\ndefer cancel()\nissues, err := issueops.SearchIssuesWithCounts(ctx, q, filter)","handlingStrategy":"retry","validationCode":"// sanity-check scope before a long count scan\nif ctx.Err() != nil { return ctx.Err() }\nif filter.Limit == 0 && filter.MaxRows == 0 && corpusIsLarge() {\n    filter.MaxRows = 10000 // cap the scan\n}","typeGuard":"func isCountsRowsErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \": rows: \") &&\n        strings.HasPrefix(err.Error(), \"search count \")\n}","tryCatchPattern":"var res []*types.IssueWithCounts\nvar err error\nfor attempt := 0; attempt < 3; attempt++ {\n    res, err = issueops.SearchIssuesWithCounts(ctx, q, filter)\n    if err == nil || !isCountsRowsErr(err) { break }\n    time.Sleep(backoff(attempt))\n}\nif err != nil { return err }","preventionTips":["Set context timeouts proportional to corpus size","Cap scans with filter.MaxRows on large databases","Raise server-side query timeouts for count projections","Avoid CI cancellation signals mid-scan; checkpoint large count jobs"],"tags":["database","sql","rows-iteration","counts"],"backgroundTag":"sql-rows-iteration-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}