gastownhall/beads · error

descendants: wisps filter: %w

Error message

descendants: wisps filter: %w

What it means

GetDescendants wraps a failure from buildIssueFilterClauses when building the WHERE clauses for the wisps branch of the recursive descendants CTE. The same filter was already built successfully against the issues table, so a failure here almost always means a filter clause is invalid specifically for the wisps table layout (e.g. a column exists in one table config but not the other).

Source

Thrown at internal/storage/domain/db/issue_descendants.go:67

	wispDepsExist, err := r.optionalTableExists(ctx, "wisp_dependencies")
	if err != nil {
		return nil, fmt.Errorf("descendants: wisp_dependencies probe: %w", err)
	}
	walkWisps := wispDepsExist && !filter.SkipWisps
	if walkWisps {
		empty, probeErr := r.wispsTableEmptyOrMissing(ctx)
		if probeErr != nil {
			return nil, fmt.Errorf("descendants: wisps table probe: %w", probeErr)
		}
		walkWisps = !empty
	}

	var wispWhereClauses []string
	var wispArgs []any
	if walkWisps {
		wispWhereClauses, wispArgs, err = buildIssueFilterClauses("", levelFilter, wispsFilterTables)
		if err != nil {
			return nil, fmt.Errorf("descendants: wisps filter: %w", err)
		}
	}

	issuePred := buildDescendantsPred("issues", "i", "issue_matches", issueWhereClauses, issueArgs)
	var wispPred predBundle
	if walkWisps {
		wispPred = buildDescendantsPred("wisps", "w", "wisp_matches", wispWhereClauses, wispArgs)
	}

	cte, allArgs := buildDescendantsCTE(rootID, walkWisps, issuePred, wispPred)

	rows, err := r.runner.QueryContext(ctx, cte, allArgs...)
	if err != nil {
		return nil, fmt.Errorf("descendants: query: %w", err)
	}
	page, err := scanIDSrcPage(rows)
	if err != nil {
		return nil, fmt.Errorf("descendants: %w", err)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped inner error to identify which filter clause failed to build
  2. Remove or simplify the offending filter field and re-run GetDescendants
  3. Check that the database schema matches the bd version (run bd doctor / migrations)
  4. Set SkipWisps=true in the filter if you only need durable issues and the wisps plane is problematic

Example fix

// before
issues, err := repo.GetDescendants(ctx, root, types.IssueFilter{Assignee: &a, Ephemeral: &t})
// after: drop fields unsupported on the wisps plane, or scope to issues only
issues, err := repo.GetDescendants(ctx, root, types.IssueFilter{Assignee: &a, SkipWisps: true})
Defensive patterns

Strategy: validation

Validate before calling

// build the filter clauses yourself first; if this fails, the filter is bad
if _, _, err := sqlbuild.BuildIssueFilterClauses("", filter, sqlbuild.WispsFilterTables); err != nil {
    return fmt.Errorf("filter unsupported on wisps plane: %w", err)
}

Try / catch

issues, err := repo.GetDescendants(ctx, rootID, filter)
if err != nil && strings.Contains(err.Error(), "wisps filter") {
    // fall back to durable issues only
    filter.SkipWisps = true
    issues, err = repo.GetDescendants(ctx, rootID, filter)
}

Prevention

When it happens

Trigger: Calling GetDescendants with an IssueFilter whose clauses reference wisp-incompatible fields, when the wisps table exists and is non-empty and SkipWisps is false — i.e. buildIssueFilterClauses("", levelFilter, wispsFilterTables) returns an error.

Common situations: Using a filter field supported on durable issues but not on the wisps plane after a schema/config divergence; version mismatch where an older database lacks a column the filter builder expects in wispsFilterTables.

Related errors


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