jaegertracing/jaeger · error

%w: it compares %q against a duration such as "2s" only

Error message

%w: it compares %q against a duration such as "2s" only

What it means

A bound on the span.duration built-in field must be a *expression.DurationValue (a length of time such as "2s"). When the constant is neither a DurationValue nor an untyped AnyValue (that case yields ErrFilterInvalid instead), the comparison asks for a value type durations cannot have — a number or string constant — and is refused with ErrFilterUnsupported. This guards the legacy duration fields DurationMin/DurationMax from being assigned non-duration constants.

Source

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

	switch constant := value.(type) {
	case *expression.StringValue:
		return constant.Value, nil
	case *expression.AnyValue:
		return constant.Value, nil
	default:
		return "", fmt.Errorf("%w: it compares %q against a string constant only", ErrFilterUnsupported, name)
	}
}

// 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. Use a duration literal/spelling so ResolveConstants produces a *expression.DurationValue (e.g. "2s", "500ms").
  2. Construct the value explicitly as &expression.DurationValue{Value: d} from a time.Duration.
  3. Move the numeric comparison to an attribute predicate or a backend with native typed filter support.

Example fix

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

Strategy: validation

Validate before calling

func durationBoundFromString(s string) (*expression.DurationValue, error) {
  d, err := time.ParseDuration(s)
  if err != nil { return nil, err }
  return &expression.DurationValue{Value: d}, nil
}

Type guard

func isDurationValue(v expression.Expression) bool { _, ok := v.(*expression.DurationValue); return ok }

Try / catch

legacy, err := params.ToLegacyShape()
if errors.Is(err, tracestore.ErrFilterUnsupported) {
  return fmt.Errorf("duration bounds must be durations like \"2s\", not numbers or strings: %w", err)
}

Prevention

When it happens

Trigger: ToLegacyShape() downconversion sees span.duration >=/<= with a typed constant that is not a duration, e.g. a StringValue{"2s"} or numeric constant on the duration field.

Common situations: A client writes duration > 1000 (number) instead of duration > 1s; a UI sends the bound as a plain string constant; mismatched expression construction helpers.

Related errors


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