gastownhall/beads · error
unexpected node type: %T
Error message
unexpected node type: %T
What it means
buildFilter encountered an AST node type it has no case for; only ComparisonNode, AndNode, NotNode and the optimizable OrNode are supported. This is an internal invariant violation — it means the parser produced (or a caller constructed) a node kind the filter builder doesn't understand, and the %T verb names the offending type.
Source
Thrown at internal/query/evaluator.go:152
case *ComparisonNode:
return e.applyComparison(n, filter)
case *AndNode:
if err := e.buildFilter(n.Left, filter); err != nil {
return err
}
return e.buildFilter(n.Right, filter)
case *NotNode:
return e.applyNot(n, filter)
case *OrNode:
// Only reached for LabelsAny optimization
labels := e.collectOrLabels(n)
if labels != nil {
filter.LabelsAny = append(filter.LabelsAny, labels...)
return nil
}
return fmt.Errorf("OR not supported for this field combination")
default:
return fmt.Errorf("unexpected node type: %T", node)
}
}
// applyComparison applies a comparison to the filter.
func (e *Evaluator) applyComparison(comp *ComparisonNode, filter *types.IssueFilter) error {
switch comp.Field {
case "status":
return e.applyStatusFilter(comp, filter)
case "priority":
return e.applyPriorityFilter(comp, filter)
case "type":
return e.applyTypeFilter(comp, filter)
case "assignee":
return e.applyAssigneeFilter(comp, filter)
case "owner":
return e.applyOwnerFilter(comp, filter)
case "label", "labels":
return e.applyLabelFilter(comp, filter)View on GitHub (pinned to 71377f2769)
Solutions
- Read the %T type in the message and remove or rewrite that node kind before calling Evaluate
- Restrict programmatically built ASTs to ComparisonNode, AndNode, NotNode and label-only OrNodes
- Update the evaluator/parser together — ensure both sides come from the same library version
- If you need the construct, extend buildFilter with a case rather than passing it through
Example fix
// before
node := &CustomGroupNode{Children: children}
evaluator.Evaluate(node)
// after
node := &AndNode{Left: left, Right: right} // supported node type
evaluator.Evaluate(node) Defensive patterns
Strategy: type-guard
Validate before calling
// Ensure the AST contains only node kinds buildFilter understands
func filterSafe(n query.Node) bool {
switch t := n.(type) {
case *query.ComparisonNode, *query.AndNode, *query.NotNode:
return true
case *query.OrNode:
return filterSafe(t.Left) && filterSafe(t.Right)
default:
return false
}
} Type guard
func isFilterNode(n query.Node) bool {
switch n.(type) {
case *query.ComparisonNode, *query.AndNode, *query.NotNode, *query.OrNode:
return true
default:
return false
}
} Try / catch
if err := evaluator.Evaluate(ast); err != nil {
if strings.Contains(err.Error(), "unexpected node type") {
return fmt.Errorf("internal: query AST contains unsupported node: %w", err)
}
return err
} Prevention
- Build filter ASTs only with the exported node constructors of the same library version
- Keep parser and evaluator versions in lockstep; never mix across versions
- Pre-validate ASTs with a node-type whitelist before calling Evaluate
- When adding new node types, update buildFilter's switch in the same change
When it happens
Trigger: Passing an AST containing node types outside the filter-supported subset (e.g. grouping/nesting node kinds, custom Node implementations) into Evaluate/buildFilter; a parser bug emitting a new node type after a grammar change.
Common situations: Constructing query ASTs programmatically with unsupported node types; version skew where a newer parser emits nodes an older evaluator doesn't handle; third-party code implementing the Node interface.
Related errors
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/9f4c6e60b409066c.
Report an issue: GitHub.