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
- Read the wrapped cause — the builder names the rejected filter field/value.
- Strip or simplify the IssueFilter passed to GetDescendants; filter results in Go instead of SQL.
- Verify status/priority values against types constants (not raw strings).
- 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
- Build filters from types constants, never raw strings.
- Keep the filter minimal for descendants queries; post-filter in application code.
- After library upgrades, re-check that all populated filter fields are supported by BuildIssueFilterClauses.
- Unit-test filter construction paths your code uses.
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
- no store is open for this workspace
- not found
- no absolute native user directory is available
- ExternalDoltConfig: set either Socket OR (Host, Port), not b
- ExternalDoltConfig: must set Socket or (Host, Port)
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/3328607f4a47681e.
Report an issue: GitHub.