{"record":{"id":"7b7c2ba299b7e6d6","repo":"gastownhall/beads","slug":"descendants-hydrate-wisps-w","errorCode":null,"errorMessage":"descendants: hydrate wisps: %w","messagePattern":"descendants: hydrate wisps: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/issue_descendants.go","lineNumber":97,"sourceCode":"\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\n// buildDescendantsCTE walks parent-child edges AND the dotted-ID fallback the\n// classic ParentID filter applies (issueops/filters.go): a row named\n// <node>.<suffix> with no parent-child edge at all is a child of <node>.\n// Rows carry a via marker: 'e' for edge-found, 'd' for dotted-found. Dotted\n// recursion only fires from 'e' rows — a dotted node's own dotted\n// descendants share its prefix and were already matched by whichever LIKE\n// found it, so re-expanding them would only multiply duplicate rows.\n// Filter predicates are hoisted into named matches CTEs (see predBundle) to\n// dodge a dolt 2.1.6 analyzer bug.\nfunc buildDescendantsCTE(rootID string, walkWisps bool, issuePred, wispPred predBundle) (string, []any) {\n\tvar b strings.Builder\n\tvar args []any","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/issue_descendants.go#L79-L115","documentation":"GetDescendants wraps a failure from fetchIssuesByIDs when hydrating wisp rows matched by the descendants walk. The wisps plane is optional, so a table-not-exist error is deliberately tolerated (wispsByID stays nil); only other failures — connection drops, scan errors, hydration failures on wisp_labels/wisp_dependencies — are wrapped and returned as \"descendants: hydrate wisps\".","triggerScenarios":"Calling GetDescendants on a store where wisps exist (the walk found wisp IDs) but hydrating them fails for a reason other than the wisps/wisp_labels/wisp_dependencies tables being absent: e.g. wisp_labels is missing while wisps is present, or the connection died mid-fetch.","commonSituations":"Partially migrated databases where wisps exists but wisp_labels/wisp_dependencies do not — wait, the latter are tolerated only via MissingTableName on any table; a schema where wisps exists but its companion tables were dropped inconsistently; transient connection failure during hydration.","solutions":["Read the wrapped inner error; if it is a table-not-exist for an optional wisp table it would have been swallowed, so this indicates a different failure","Run bd doctor to check wisp-plane table consistency (wisps, wisp_labels, wisp_dependencies)","Retry on transient connection errors","If the wisp plane is unusable and unwanted, re-run with SkipWisps=true to bypass wisp hydration entirely"],"exampleFix":"// before\nissues, err := repo.GetDescendants(ctx, root, filter)\n// after: bypass a broken optional wisp plane when only durable issues are needed\nissues, err := repo.GetDescendants(ctx, root, types.IssueFilter{SkipWisps: true})","handlingStrategy":"fallback","validationCode":null,"typeGuard":"func isWispPlaneBroken(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"hydrate wisps\") && !dberrors.IsTableNotExist(err)\n}","tryCatchPattern":"issues, err := repo.GetDescendants(ctx, rootID, filter)\nif isWispPlaneBroken(err) {\n    // degrade gracefully: durable issues only\n    issues, err = repo.GetDescendants(ctx, rootID, types.IssueFilter{SkipWisps: true})\n}","preventionTips":["Treat the wisps plane as optional; always handle its absence gracefully","Run bd doctor to keep wisps, wisp_labels, wisp_dependencies consistent","Use SkipWisps=true when ephemeral data is not needed","Verify full schema after clones or restores"],"tags":["database","wisps","hydration","schema"],"backgroundTag":"missing-table","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}