jaegertracing/jaeger · error · ErrFilterUnsupported
%w: it does not support the built-in field %q of the %q leve
Error message
%w: it does not support the built-in field %q of the %q level
What it means
When a TraceQueryParams carries a Filter but the Reader does not support filters, the query is rewritten into the legacy scalar fields (service name, operation name, duration bounds, attribute map). Only three built-in fields have a legacy equivalent; a predicate on any other built-in field (e.g. span status, span kind, event count) has nowhere to go and is refused with ErrFilterUnsupported wrapping this message. This is a static capability limit of the legacy-shape fallback, not a data or configuration problem.
Source
Thrown at internal/storage/v2/api/tracestore/shape.go:208
if _, ok := query.Attributes.Get(ref.Key); ok {
return errRepeatedPredicateOn(ref.Key)
}
query.Attributes.PutStr(ref.Key, text)
return nil
}
// applyField writes a predicate on a built-in field into the legacy field that holds it. Only
// three of the built-ins have one, and a predicate on any of the others is refused.
func applyField(query *TraceQueryParams, op expression.Operator, ref *expression.FieldRef, value expression.Expression) error {
switch {
case isField(ref, expression.LevelResource, expression.ResourceFieldService):
return applyText(&query.ServiceName, op, ref.Name, value)
case isField(ref, expression.LevelSpan, expression.SpanFieldName):
return applyText(&query.OperationName, op, ref.Name, value)
case isField(ref, expression.LevelSpan, expression.SpanFieldDuration):
return applyDurationBound(query, op, ref.Name, value)
default:
return fmt.Errorf("%w: it does not support the built-in field %q of the %q level",
ErrFilterUnsupported, ref.Name, ref.Level)
}
}
// isField reports whether the reference names that built-in field. It takes both the level and
// the name because neither identifies a field on its own.
func isField(ref *expression.FieldRef, level expression.Level, name string) bool {
return ref.Level == level && ref.Name == name
}
// applyText writes an equality on a string-valued legacy field.
func applyText(target *string, op expression.Operator, name string, value expression.Expression) error {
if op != expression.OpEq {
return errUnsupportedOperatorOn(op, name)
}
text, err := textConstant(name, value)
if err != nil {
return errView on GitHub (pinned to 806f444784)
Solutions
- Rewrite the predicate as an AttributeRef or restrict the filter to the three supported built-in fields (resource service, span name, span duration).
- Use a Reader/backend that declares native filter support so the filter is passed through instead of downconverted.
- Handle errors.Is(err, tracestore.ErrFilterUnsupported) and surface a 'query not supported by this backend' message to the user instead of retrying.
Example fix
// before
filter := &expression.Call{Op: expression.OpEq, Args: []expression.Expression{
&expression.FieldRef{Level: expression.LevelSpan, Name: expression.SpanFieldStatus},
&expression.StringValue{Value: "error"}}}
// after
filter := &expression.Call{Op: expression.OpEq, Args: []expression.Expression{
&expression.AttributeRef{Key: "error", Level: expression.LevelSpan},
&expression.StringValue{Value: "true"}}} Defensive patterns
Strategy: validation
Validate before calling
func usesOnlyLegacyFields(f *expression.Call) bool {
preds, err := flatten(f)
if err != nil { return false }
for _, p := range preds {
ref, ok := p.Args[0].(*expression.FieldRef)
if !ok { continue }
ok = (ref.Level == expression.LevelResource && ref.Name == expression.ResourceFieldService) ||
(ref.Level == expression.LevelSpan && ref.Name == expression.SpanFieldName) ||
(ref.Level == expression.LevelSpan && ref.Name == expression.SpanFieldDuration)
if !ok { return false }
}
return true
} Type guard
func isLegacyFieldRef(e expression.Expression) bool {
ref, ok := e.(*expression.FieldRef)
if !ok { return false }
switch {
case ref.Level == expression.LevelResource && ref.Name == expression.ResourceFieldService: return true
case ref.Level == expression.LevelSpan && ref.Name == expression.SpanFieldName: return true
case ref.Level == expression.LevelSpan && ref.Name == expression.SpanFieldDuration: return true
}
return false
} Try / catch
qp, err := params.ToLegacyShape()
if err != nil {
if errors.Is(err, tracestore.ErrFilterUnsupported) {
return fmt.Errorf("backend cannot serve this filter: %w", err)
}
return err
} Prevention
- Restrict filter builders to the three legacy built-in fields when the backend lacks native filter support
- Check the Reader's filter capabilities before constructing advanced field predicates
- Treat ErrFilterUnsupported as a capability error and surface it, never retry
When it happens
Trigger: Calling TraceQueryParams.ToLegacyShape() (directly or via a Reader lacking filter support) with a Filter whose flat conjunction contains a *expression.FieldRef predicate whose (Level, Name) is not (LevelResource, service), (LevelSpan, name), or (LevelSpan, duration) — e.g. span:status = 'error'.
Common situations: A query interceptor or UI builds a filter on a built-in field like span.status or span.kind assuming full field support; the backend only declares legacy scalar support; version drift where newer expression fields are sent to an older backend path.
Related errors
- %w: it compares %q against a string constant only
- %w: it compares %q against a duration such as "2s" only
- %w: it does not support the operator %q
- %w: it does not support the operator %q on %q
- %w: it can carry only one predicate on %q
AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01).
Data as JSON: /api/errors/ed7e4a662cfdaa42.
Report an issue: GitHub.