jaegertracing/jaeger · error

%w: it does not support the operator %q on %q

Error message

%w: it does not support the operator %q on %q

What it means

The legacy fields a filter is downconverted into accept exactly one operator each: attributes, service name and operation name accept only equality (OpEq), and span.duration accepts only inclusive bounds OpGte/OpLte. Any other operator on that field — e.g. != on a tag, > on a name, < on a duration — has no legacy representation and is refused with ErrFilterUnsupported naming the operator and field. The narrower errUnsupportedOperator variant is used when no field name is known.

Source

Thrown at internal/storage/v2/api/tracestore/shape.go:291

// errNotADuration refuses a bound that is not a length of time. An untyped constant reaching here
// was never read as a duration, which expression.ResolveConstants does on the way in, so the
// refusal says that rather than blaming the spelling the caller wrote.
func errNotADuration(name string, value expression.Expression) error {
	if constant, ok := value.(*expression.AnyValue); ok {
		return fmt.Errorf("%w: the bound %q on %q was never read as a duration",
			ErrFilterInvalid, constant.Value, name)
	}
	return fmt.Errorf(`%w: it compares %q against a duration such as "2s" only`,
		ErrFilterUnsupported, name)
}

func errUnsupportedOperator(op expression.Operator) error {
	return fmt.Errorf("%w: it does not support the operator %q", ErrFilterUnsupported, op)
}

func errUnsupportedOperatorOn(op expression.Operator, name string) error {
	return fmt.Errorf("%w: it does not support the operator %q on %q", ErrFilterUnsupported, op, name)
}

func errRepeatedPredicateOn(name string) error {
	return fmt.Errorf("%w: it can carry only one predicate on %q", ErrFilterUnsupported, name)
}

View on GitHub (pinned to 806f444784)

Solutions

  1. Rewrite as equality where possible (e.g. negate client-side, or use the exact matching value).
  2. Use inclusive bounds for duration: duration >= X / duration <= Y instead of strict > / <.
  3. Target a backend that declares native filter/operator support so no downconversion happens.

Example fix

// before
&expression.Call{Op: expression.OpGt, Args: []expression.Expression{durField, &expression.DurationValue{Value: time.Second}}}
// after
&expression.Call{Op: expression.OpGte, Args: []expression.Expression{durField, &expression.DurationValue{Value: time.Second}}}
Defensive patterns

Strategy: validation

Validate before calling

func allowedOp(op expression.Operator, ref expression.Expression) bool {
  switch ref.(type) {
  case *expression.AttributeRef:
    return op == expression.OpEq
  case *expression.FieldRef:
    fr := ref.(*expression.FieldRef)
    if fr.Name == expression.SpanFieldDuration {
      return op == expression.OpGte || op == expression.OpLte
    }
    return op == expression.OpEq
  }
  return false
}

Try / catch

legacy, err := params.ToLegacyShape()
if errors.Is(err, tracestore.ErrFilterUnsupported) {
  return nil, fmt.Errorf("legacy fields support only =, and >=/<= for duration: %w", err)
}

Prevention

When it happens

Trigger: ToLegacyShape() sees an AttributeRef predicate with op != OpEq; a service/name FieldRef predicate with op != OpEq; or a duration FieldRef predicate with an op other than OpGte/OpLte.

Common situations: Users write attribute != value or attribute > value searches in a UI; exclusive bounds duration > 1s are requested from a legacy backend; a filter builder emits regex-like operators unsupported by the legacy shape.

Related errors


AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01). Data as JSON: /api/errors/311173b4364632b6. Report an issue: GitHub.