gastownhall/beads · error

descendants: issues filter: %w

Error message

descendants: issues filter: %w

What it means

Wraps a failure from buildIssueFilterClauses while constructing the WHERE clauses for the issues side of a descendants walk in GetDescendants. Clause building validates filter fields; an unsupported filter combination or invalid value fails here before any SQL runs. The resulting error prevents the recursive descendant query from being built.

Source

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

		return predBundle{}
	}
	return predBundle{
		matchesCTE: fmt.Sprintf("%s AS (SELECT id FROM %s WHERE %s)",
			cteName, table, strings.Join(clauses, " AND ")),
		snippet: fmt.Sprintf(" AND %s.id IN (SELECT id FROM %s)", alias, cteName),
		args:    args,
	}
}

func (r *issueSQLRepositoryImpl) GetDescendants(ctx context.Context, rootID string, filter types.IssueFilter) ([]*types.Issue, error) {
	levelFilter := filter
	levelFilter.ParentID = nil
	levelFilter.Limit = 0
	levelFilter.Offset = 0

	issueWhereClauses, issueArgs, err := buildIssueFilterClauses("", levelFilter, issuesFilterTables)
	if err != nil {
		return nil, fmt.Errorf("descendants: issues filter: %w", err)
	}

	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 {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped cause — the builder names the rejected filter field/value.
  2. Strip or simplify the IssueFilter passed to GetDescendants; filter results in Go instead of SQL.
  3. Verify status/priority values against types constants (not raw strings).
  4. Check for version mismatch between your filter struct fields and the installed storage library.

Example fix

// before
filter.Status = []string{"opn"} // typo'd status
deps, err := repo.GetDescendants(ctx, epicID, filter)
// after
filter.Status = []string{types.StatusOpen.String()}
deps, err := repo.GetDescendants(ctx, epicID, filter)
Defensive patterns

Strategy: validation

Validate before calling

// validate the filter yourself before GetDescendants
for _, s := range filter.Status {
    if !types.IsValidStatus(s) { return fmt.Errorf("invalid status %q", s) }
}

Try / catch

deps, err := repo.GetDescendants(ctx, id, filter)
if err != nil {
    if strings.Contains(err.Error(), "descendants: issues filter") {
        return getDescendantsUnfiltered(ctx, id) // fallback: filter in Go
    }
    return err
}

Prevention

When it happens

Trigger: Calling GetDescendants with an IssueFilter that BuildIssueFilterClauses cannot translate: unsupported field, invalid status/priority values, or a filter combination the shared SQL builder rejects. Level/parent/limit fields are deliberately zeroed first, so those cannot be the cause — the caller's remaining filter fields are.

Common situations: Passing a filter populated for a different API (fields unsupported in descendants context), typo'd or out-of-range status/priority values, or an upgraded library where new filter fields are not yet handled by the builder.

Related errors


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