{"record":{"id":"9a673901520c3c64","repo":"gastownhall/beads","slug":"search-issues-with-counts-ephemeral-wisp-probe-9a6739","errorCode":null,"errorMessage":"search issues with counts: ephemeral wisp probe: %w","messagePattern":"search issues with counts: ephemeral wisp probe: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/search_counts.go","lineNumber":22,"sourceCode":"\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"sort\"\n\n\t\"github.com/steveyegge/beads/internal/storage/sqlbuild\"\n\t\"github.com/steveyegge/beads/internal/types\"\n)\n\nfunc SearchIssuesWithCountsInTx(ctx context.Context, tx *sql.Tx, query string, filter types.IssueFilter) ([]*types.IssueWithCounts, error) {\n\twispDepsExist, err := optionalTableExistsInTx(ctx, tx, \"wisp_dependencies\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"search issues with counts: wisp dependency probe: %w\", err)\n\t}\n\n\tif filter.Ephemeral != nil && *filter.Ephemeral {\n\t\tempty, probeErr := wispsTableEmptyOrMissingInTx(ctx, tx)\n\t\tif probeErr != nil {\n\t\t\treturn nil, fmt.Errorf(\"search issues with counts: ephemeral wisp probe: %w\", probeErr)\n\t\t}\n\t\tif !empty && wispDepsExist {\n\t\t\twisps, err := runFilterSearchQueryInTx(ctx, tx, query, filter, WispsFilterTables, true)\n\t\t\tif err != nil && !missingOptionalWispTable(err) {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif len(wisps) > 0 {\n\t\t\t\treturn finishSearchIssuesWithCounts(wisps, filter)\n\t\t\t}\n\t\t}\n\t\t// Fall through: the wisps tier is missing/empty or matched no rows.\n\t\t// Mirror SearchIssuesInTx / CountIssuesInTx so count-projection searches\n\t\t// also surface a durable issues-table row flagged ephemeral=1 instead of\n\t\t// dropping it. Use the same IssuesFilterTables query the non-ephemeral\n\t\t// path uses, keeping the GH#4387 count/list cardinality parity for\n\t\t// searches that project counts (e.g. `bd search --counts --include-infra`).\n\t\tout, err := runFilterSearchQueryInTx(ctx, tx, query, filter, IssuesFilterTables, wispDepsExist)\n\t\tif err != nil {","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/search_counts.go#L4-L40","documentation":"SearchIssuesWithCountsInTx wraps a failure of wispsTableEmptyOrMissingInTx when filter.Ephemeral is true. The ephemeral path first probes whether the wisps table is empty or absent before querying it; if that probe query fails (as opposed to reporting empty/missing, which is handled), the search returns this wrapped error.","triggerScenarios":"Calling SearchIssuesWithCountsInTx with filter.Ephemeral = &true where the wisps emptiness probe SQL errors — DB locked, connection dropped, ctx cancelled, or catalog introspection failure.","commonSituations":"Concurrent writer holding the SQLite write lock; remote Dolt server outage; deadline too short for the probe plus search; corrupted DB.","solutions":["Unwrap probeErr for the real driver error; fix that root cause","Check for concurrent writers/locks on the database file","Retry the search; probe failures are typically transient (locks, connection)","Raise the context timeout for slow or remote databases","Verify DB integrity with bd doctor if failures persist"],"exampleFix":"// before: ephemeral search against a DB locked by a bulk import\nfilter.Ephemeral = &yes\nres, err := searchWithCounts(ctx, tx, q, filter) // probe fails on lock\n// after: wait/retry around the writer, or run after import completes\n<-importDone\nres, err := searchWithCounts(ctx, tx, q, filter)","handlingStrategy":"retry","validationCode":"// ensure the wisps table is at least introspectable before an ephemeral search\nvar name string\nerr := tx.QueryRowContext(ctx,\n    \"SELECT name FROM sqlite_master WHERE type='table' AND name='wisps'\").Scan(&name)\nif err != nil && !errors.Is(err, sql.ErrNoRows) {\n    return fmt.Errorf(\"wisps introspection broken: %w\", err)\n}","typeGuard":"func isEphemeralProbeErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(),\n        \"search issues with counts: ephemeral wisp probe: \")\n}","tryCatchPattern":"res, err := issueops.SearchIssuesWithCountsInTx(ctx, tx, q, filter)\nif err != nil && isEphemeralProbeErr(err) {\n    time.Sleep(100 * time.Millisecond) // lock contention often clears\n    res, err = issueops.SearchIssuesWithCountsInTx(ctx, tx, q, filter)\n}","preventionTips":["Schedule ephemeral searches outside bulk-write windows","Use WAL mode / short write transactions to reduce lock contention","Retry transient failures with small backoff","Verify DB integrity periodically with bd doctor"],"tags":["database","sql","wisps","schema-probe"],"backgroundTag":"sql-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}