{"record":{"id":"ebf5617a1632501e","repo":"gastownhall/beads","slug":"search-s-pattern-b-w","errorCode":null,"errorMessage":"search %s (pattern B): %w","messagePattern":"search (.+?) \\(pattern B\\): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/search.go","lineNumber":487,"sourceCode":"\t\t}\n\t\titemMap[proj.id(item)] = item\n\t}\n\t_ = fetchRows.Close()\n\tif err := fetchRows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"search %s (hydrate): rows: %w\", tables.Main, err)\n\t}\n\n\t// Reorder to preserve the id-scan ORDER BY.\n\tresults := make([]T, 0, len(ids))\n\tfor _, id := range ids {\n\t\tif item, ok := itemMap[id]; ok {\n\t\t\tresults = append(results, item)\n\t\t}\n\t}\n\n\tif proj.hydrate != nil && len(results) > 0 {\n\t\tif err := proj.hydrate(ctx, tx, tables, results, filter); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"search %s (pattern B): %w\", tables.Main, err)\n\t\t}\n\t}\n\n\treturn results, nil\n}\n\n// EffectiveSearchLimit returns the SQL LIMIT to apply given a caller-supplied\n// Limit and a defensive MaxRows cap. Semantics (architecture be-jp5s D1/R-03):\n//\n//   - limit=0, maxRows=0: returns 0 (no LIMIT clause; unlimited)\n//   - limit=N, maxRows=0: returns N (today's --limit behavior)\n//   - limit=0, maxRows=M: returns M+1 (detect overage at cap+1)\n//   - limit=N, maxRows=M: returns N if N<=M, else M+1\n//\n// Callers issue LIMIT cap+1 specifically so that EnforceMaxRowsCap can detect\n// overage by comparing len(scanned rows) to MaxRows. The returned int is the\n// LIMIT value; treat 0 as \"do not emit a LIMIT clause\".\nfunc EffectiveSearchLimit(limit, maxRows int) int {","sourceCodeStart":469,"sourceCodeEnd":505,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/search.go#L469-L505","documentation":"searchTablePatternBT wraps a failure from the projection's hydrate callback after a successful Pattern B id scan + batch fetch. The rows were retrieved and reordered, but post-fetch enrichment (dependencies, labels, counts, etc.) inside the same transaction failed. Identical root-cause family to the plain-path hydrate wrapper, but tagged '(pattern B)'.","triggerScenarios":"searchTablePatternBT completed the id scan and batch fetch, results are non-empty, proj.hydrate != nil, and the hydration queries error — missing auxiliary table, poisoned tx, or ctx cancellation during hydration.","commonSituations":"Schema version drift dropping a table the hydrator joins (e.g. wisp_dependencies on older DBs); lock contention during hydration; deadline exceeded hydrating many rows after a large limit.","solutions":["Unwrap the inner error to find which hydration query failed and fix that root cause","Run schema migration so all auxiliary tables exist (bd migrate / bd doctor)","Reduce filter.Limit / set MaxRows to shrink hydration work if the cause is a timeout","Retry on a fresh transaction if the tx was invalidated by an earlier statement error","Set the relevant SkipLabels/SkipCounts/Lite filter flags to bypass hydration legs you do not need"],"exampleFix":"// before: hydrating deps+labels+counts for 10k rows under a 2s deadline\nfilter.Limit = 10000\n// after: trim the work\nctx, cancel := context.WithTimeout(ctx, 30*time.Second)\ndefer cancel()\nfilter.Limit = 100\nfilter.SkipCounts = true\nissues, err := searchIssues(ctx, tx, q, filter)","handlingStrategy":"try-catch","validationCode":"// confirm hydration prerequisites exist before a Pattern B search\nfor _, t := range []string{\"dependencies\", \"labels\", \"wisp_dependencies\"} {\n    var one int\n    err := tx.QueryRowContext(ctx,\n        \"SELECT 1 FROM sqlite_master WHERE type='table' AND name=?\", t).Scan(&one)\n    if err != nil && !errors.Is(err, sql.ErrNoRows) {\n        return fmt.Errorf(\"schema probe failed: %w\", err)\n    }\n}","typeGuard":"func isPatternBHydrateErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"(pattern B): \")\n}","tryCatchPattern":"res, err := searchIssues(ctx, tx, q, filter)\nif err != nil && isPatternBHydrateErr(err) {\n    // degrade gracefully: retry without hydration legs\n    filter.SkipCounts = true\n    filter.SkipLabels = true\n    res, err = searchIssues(ctx, tx, q, filter)\n}","preventionTips":["Migrate schema so optional hydration tables exist","Trim hydration work with SkipLabels/SkipCounts/Lite when possible","Size deadlines to the hydrated row count","Retry on a fresh transaction if a prior statement poisoned the tx"],"tags":["database","sql","hydration","pattern-b"],"backgroundTag":"sql-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}