{"record":{"id":"98b2df2027b24970","repo":"jaegertracing/jaeger","slug":"w-the-bound-q-on-q-was-never-read-as-a-duratio","errorCode":null,"errorMessage":"%w: the bound %q on %q was never read as a duration","messagePattern":"%w: the bound %q on %q was never read as a duration","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/v2/api/tracestore/shape.go","lineNumber":279,"sourceCode":"// untyped one, which is what an unqualified tag equality has always been. A constant of any other\n// type asks for a match on a type these fields cannot name.\nfunc textConstant(name string, value expression.Expression) (string, error) {\n\tswitch constant := value.(type) {\n\tcase *expression.StringValue:\n\t\treturn constant.Value, nil\n\tcase *expression.AnyValue:\n\t\treturn constant.Value, nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"%w: it compares %q against a string constant only\", ErrFilterUnsupported, name)\n\t}\n}\n\n// errNotADuration refuses a bound that is not a length of time. An untyped constant reaching here\n// was never read as a duration, which expression.ResolveConstants does on the way in, so the\n// refusal says that rather than blaming the spelling the caller wrote.\nfunc errNotADuration(name string, value expression.Expression) error {\n\tif constant, ok := value.(*expression.AnyValue); ok {\n\t\treturn fmt.Errorf(\"%w: the bound %q on %q was never read as a duration\",\n\t\t\tErrFilterInvalid, constant.Value, name)\n\t}\n\treturn fmt.Errorf(`%w: it compares %q against a duration such as \"2s\" only`,\n\t\tErrFilterUnsupported, name)\n}\n\nfunc errUnsupportedOperator(op expression.Operator) error {\n\treturn fmt.Errorf(\"%w: it does not support the operator %q\", ErrFilterUnsupported, op)\n}\n\nfunc errUnsupportedOperatorOn(op expression.Operator, name string) error {\n\treturn fmt.Errorf(\"%w: it does not support the operator %q on %q\", ErrFilterUnsupported, op, name)\n}\n\nfunc errRepeatedPredicateOn(name string) error {\n\treturn fmt.Errorf(\"%w: it can carry only one predicate on %q\", ErrFilterUnsupported, name)\n}\n","sourceCodeStart":261,"sourceCodeEnd":297,"githubUrl":"https://github.com/jaegertracing/jaeger/blob/806f4447841ecdb60519f408b004a599d515f437/internal/storage/v2/api/tracestore/shape.go#L261-L297","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n&expression.Call{Op: expression.OpGte, Args: []expression.Expression{durField, &expression.AnyValue{Value: \"2s\"}}}\n// after\nresolved := expression.ResolveConstants(filter) // AnyValue under span.duration becomes DurationValue\n_ = resolved","handlingStrategy":"validation","validationCode":"func ensureDurationResolved(f *expression.Call) error {\n  for _, p := range conjuncts(f) {\n    if ref, ok := p.Args[0].(*expression.FieldRef); ok && ref.Name == expression.SpanFieldDuration {\n      if _, ok := p.Args[1].(*expression.DurationValue); !ok {\n        return fmt.Errorf(\"span.duration bound must be a DurationValue, got %T\", p.Args[1])\n      }\n    }\n  }\n  return nil\n}","typeGuard":"func isDurationValue(v expression.Expression) bool { _, ok := v.(*expression.DurationValue); return ok }","tryCatchPattern":"legacy, err := params.ToLegacyShape()\nif errors.Is(err, tracestore.ErrFilterInvalid) {\n  return fmt.Errorf(\"duration constant was not resolved; run expression.ResolveConstants first: %w\", err)\n}","preventionTips":["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"],"tags":["jaeger","tracestore","invalid-filter","duration-parse"],"backgroundTag":"unresolved-constant-type","analyzedSha":"806f4447841ecdb60519f408b004a599d515f437","analyzedAt":"2026-09-01T02:39:22.140Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}