{"record":{"id":"9c622f3970637751","repo":"gastownhall/beads","slug":"descendants-w","errorCode":null,"errorMessage":"descendants: %w","messagePattern":"descendants: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/issue_descendants.go","lineNumber":85,"sourceCode":"\t\t\treturn nil, fmt.Errorf(\"descendants: wisps filter: %w\", err)\n\t\t}\n\t}\n\n\tissuePred := buildDescendantsPred(\"issues\", \"i\", \"issue_matches\", issueWhereClauses, issueArgs)\n\tvar wispPred predBundle\n\tif walkWisps {\n\t\twispPred = buildDescendantsPred(\"wisps\", \"w\", \"wisp_matches\", wispWhereClauses, wispArgs)\n\t}\n\n\tcte, allArgs := buildDescendantsCTE(rootID, walkWisps, issuePred, wispPred)\n\n\trows, err := r.runner.QueryContext(ctx, cte, allArgs...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"descendants: query: %w\", err)\n\t}\n\tpage, err := scanIDSrcPage(rows)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"descendants: %w\", err)\n\t}\n\n\tissuesByID, err := r.fetchIssuesByIDs(ctx, page.issueIDs, issuesFilterTables, filter)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"descendants: hydrate issues: %w\", err)\n\t}\n\n\tvar wispsByID map[string]*types.Issue\n\tif len(page.wispIDs) > 0 {\n\t\twispsByID, err = r.fetchIssuesByIDs(ctx, page.wispIDs, wispsFilterTables, filter)\n\t\tif err != nil && !dberrors.IsTableNotExist(err) {\n\t\t\treturn nil, fmt.Errorf(\"descendants: hydrate wisps: %w\", err)\n\t\t}\n\t}\n\n\treturn reassembleBySrc(page.ordered, issuesByID, wispsByID), nil\n}\n","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/issue_descendants.go#L67-L103","documentation":"GetDescendants wraps a failure from scanIDSrcPage, which reads the (id, src) rows produced by the descendants CTE and splits them into issue IDs and wisp IDs. The scan loop can fail on rows.Scan (a column type mismatch) or on rows.Err() (a mid-iteration connection/engine failure). Since the query only projects two string columns, this usually means the stream broke mid-read.","triggerScenarios":"Calling GetDescendants when the result stream from the recursive CTE fails while being read: connection dropped between QueryContext and full row consumption, driver scan error, or ctx canceled during iteration.","commonSituations":"Large hierarchies whose scan outlasts a network idle timeout; Dolt server killed mid-result; driver/driver-version mismatch producing unexpected column types.","solutions":["Check the wrapped inner error: connection errors warrant retrying the whole GetDescendants call","Reduce result size with filter.Limit/MaxRows so the scan completes within connection limits","Verify Dolt server stability (logs, memory) if failures recur on large walks","If it is a scan-type error, ensure the bd binary and database schema are the same version"],"exampleFix":"// before\nissues, err := repo.GetDescendants(ctx, root, types.IssueFilter{})\n// after: bound the walk so the result stream stays small\nissues, err := repo.GetDescendants(ctx, root, types.IssueFilter{MaxRows: 10000})","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"issues, err := repo.GetDescendants(ctx, rootID, filter)\nif err != nil && strings.HasSuffix(strings.TrimPrefix(err.Error(), \"descendants: \"), \"rows\") || strings.Contains(err.Error(), \"scan\") {\n    // result stream broke mid-read: retry the whole call\n}","preventionTips":["Bound walks with MaxRows/Limit so result streams stay small","Avoid network paths with aggressive idle timeouts during large reads","Keep driver and server versions consistent","Retry idempotent reads on stream failure"],"tags":["database","scan","rows","result-stream"],"backgroundTag":"rows-scan-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}