jaegertracing/jaeger · error
%w: the bound %q on %q was never read as a duration
Error message
%w: the bound %q on %q was never read as a duration
What it means
A duration bound on span.duration must carry a *expression.DurationValue. When the constant is untyped (*expression.AnyValue), it means the constant was never resolved as a duration at all — expression.ResolveConstants reads the span.duration field's declared type and rejects unparseable spellings on the way in, so an AnyValue here indicates the constant bypassed that resolution. Because the value is invalid rather than merely unsupported, this uses ErrFilterInvalid and reports the raw constant text plus the field name.
Source
Thrown at internal/storage/v2/api/tracestore/shape.go:279
// untyped one, which is what an unqualified tag equality has always been. A constant of any other
// type asks for a match on a type these fields cannot name.
func textConstant(name string, value expression.Expression) (string, error) {
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
- Run the filter expression through expression.ResolveConstants before passing it to ToLegacyShape / FindTraces.
- Build the value as *expression.DurationValue{Value: parsedDuration} explicitly (e.g. time.ParseDuration("2s")).
- Validate the constant parses as a duration before constructing the predicate.
Example fix
// before
&expression.Call{Op: expression.OpGte, Args: []expression.Expression{durField, &expression.AnyValue{Value: "2s"}}}
// after
resolved := expression.ResolveConstants(filter) // AnyValue under span.duration becomes DurationValue
_ = resolved Defensive patterns
Strategy: validation
Validate before calling
func ensureDurationResolved(f *expression.Call) error {
for _, p := range conjuncts(f) {
if ref, ok := p.Args[0].(*expression.FieldRef); ok && ref.Name == expression.SpanFieldDuration {
if _, ok := p.Args[1].(*expression.DurationValue); !ok {
return fmt.Errorf("span.duration bound must be a DurationValue, got %T", p.Args[1])
}
}
}
return 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.ErrFilterInvalid) {
return fmt.Errorf("duration constant was not resolved; run expression.ResolveConstants first: %w", err)
} Prevention
- Always pass filters through expression.ResolveConstants before storage
- Hand-build duration bounds as &expression.DurationValue{Value: ...}
- Unit-test filter construction paths that bypass the resolver
When it happens
Trigger: applyDurationBound (via ToLegacyShape) receives a span.duration >=/<= predicate whose value is *expression.AnyValue, i.e. a raw untyped constant string like '2s' that never went through expression.ResolveConstants.
Common situations: Hand-constructed *expression.Call filter trees that skip constant resolution; an interceptor layer that passes raw user text into the value slot; version drift between the filter builder and the resolver step.
Related errors
- %w: it does not support the built-in field %q of the %q leve
- %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
AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01).
Data as JSON: /api/errors/98b2df2027b24970.
Report an issue: GitHub.