gastownhall/beads · error

search issues with counts: wisp probe: %w

Error message

search issues with counts: wisp probe: %w

What it means

SearchIssuesWithCountsInTx wraps a failure of wispsTableEmptyOrMissingInTx on the default (non-ephemeral) path, after the durable issues query and unless filter.SkipWisps was set. The probe decides whether the wisps merge step is needed; if the probe query itself fails, the search aborts wrapped as 'wisp probe'.

Source

Thrown at internal/storage/issueops/search_counts.go:58

		if err != nil {
			return nil, err
		}
		return finishSearchIssuesWithCounts(out, filter)
	}

	out, err := runFilterSearchQueryInTx(ctx, tx, query, filter, IssuesFilterTables, wispDepsExist)
	if err != nil {
		return nil, err
	}

	// Skip wisps merge entirely when caller opts out (Q2: perf escape hatch).
	if filter.SkipWisps {
		return finishSearchIssuesWithCounts(out, filter)
	}

	empty, probeErr := wispsTableEmptyOrMissingInTx(ctx, tx)
	if probeErr != nil {
		return nil, fmt.Errorf("search issues with counts: wisp probe: %w", probeErr)
	}
	if empty {
		return finishSearchIssuesWithCounts(out, filter)
	}
	if !wispDepsExist {
		return finishSearchIssuesWithCounts(out, filter)
	}

	wisps, err := runFilterSearchQueryInTx(ctx, tx, query, filter, WispsFilterTables, true)
	if err != nil {
		if missingOptionalWispTable(err) {
			return finishSearchIssuesWithCounts(out, filter)
		}
		return nil, err
	}
	if len(wisps) == 0 {
		return finishSearchIssuesWithCounts(out, filter)
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Unwrap the cause and address the driver-level failure (lock, connection, timeout)
  2. Set filter.SkipWisps = true if you only need durable issues and can skip the wisps merge (explicit perf escape hatch)
  3. Retry the search — probes are usually transient failures
  4. Raise the context timeout for remote/slow stores
  5. Run bd doctor to check DB integrity and table accessibility

Example fix

// before: durable-only consumer still paying for (and failing on) the wisp probe
res, err := issueops.SearchIssuesWithCountsInTx(ctx, tx, q, filter)
// after: caller does not need wisps
filter.SkipWisps = true
res, err := issueops.SearchIssuesWithCountsInTx(ctx, tx, q, filter)
Defensive patterns

Strategy: fallback

Validate before calling

// skip the wisp probe entirely when wisps are not needed
if !needEphemeral {
    filter.SkipWisps = true
}

Type guard

func isWispProbeErr(err error) bool {
    return err != nil && strings.Contains(err.Error(),
        "search issues with counts: wisp probe: ")
}

Try / catch

res, err := issueops.SearchIssuesWithCountsInTx(ctx, tx, q, filter)
if err != nil && isWispProbeErr(err) {
    // fall back to durable-issues-only results
    filter.SkipWisps = true
    res, err = issueops.SearchIssuesWithCountsInTx(ctx, tx, q, filter)
}

Prevention

When it happens

Trigger: Calling SearchIssuesWithCountsInTx without SkipWisps where the wisps-table emptiness probe errors — lock contention, dropped connection, ctx cancellation, or a corrupted/inaccessible wisps table preventing even an existence/emptiness check.

Common situations: bd list/search --counts against a database where a crashed writer left the wisps table locked; remote Dolt server briefly unreachable; deadline exceeded on large remote stores.

Understand the failure class

Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/73d28aa33811e049. Report an issue: GitHub.