{"record":{"id":"47c22cd58013a537","repo":"jaegertracing/jaeger","slug":"w-w-47c22c","errorCode":null,"errorMessage":"%w: %w","messagePattern":"%w: %w","errorType":"exception","errorClass":"tracestore.ErrFilterInvalid","httpStatus":null,"severity":"error","filePath":"cmd/jaeger/internal/extension/jaegerquery/querysvc/service.go","lineNumber":218,"sourceCode":"func (qs QueryService) prepareSearchQuery(\n\tctx context.Context,\n\tquery TraceQueryParams,\n) (context.Context, TraceQueryParams, error) {\n\tif query.Filter != nil {\n\t\t// None of these refusals depends on the backend, so they come before the capability call\n\t\t// rather than after it.\n\t\tif !StructuredFiltersGate.IsEnabled() {\n\t\t\treturn ctx, query, fmt.Errorf(\"%w: enable the %q feature gate to use it\",\n\t\t\t\tErrFilterDisabled, StructuredFiltersGate.ID())\n\t\t}\n\t\tif err := query.EnsureFilterStandsAlone(); err != nil {\n\t\t\treturn ctx, query, err\n\t\t}\n\t\t// Decoding a filter validates nothing, so it is finalized — validated and normalized —\n\t\t// here, on behalf of every API layer above (RFC 0005 §7).\n\t\tfinalized, err := expression.Finalize(query.Filter)\n\t\tif err != nil {\n\t\t\treturn ctx, query, fmt.Errorf(\"%w: %w\", tracestore.ErrFilterInvalid, err)\n\t\t}\n\t\tquery.Filter = finalized\n\t}\n\tif len(qs.options.Interceptors) > 0 {\n\t\tvar err error\n\t\tctx, query, err = qs.onQuery(ctx, query)\n\t\tif err != nil {\n\t\t\treturn ctx, query, err\n\t\t}\n\t}\n\tif query.Filter == nil {\n\t\treturn ctx, query, qs.checkServiceName(ctx, query)\n\t}\n\tcaps, err := qs.traceReader.SearchCapabilities(ctx)\n\tif err != nil {\n\t\t// A reader that cannot report its capabilities reads as the least capable one, which\n\t\t// serves only the legacy predicate fields.\n\t\tcaps = tracestore.SearchCapabilities{}","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/jaegertracing/jaeger/blob/806f4447841ecdb60519f408b004a599d515f437/cmd/jaeger/internal/extension/jaegerquery/querysvc/service.go#L200-L236","documentation":"prepareSearchQuery finalizes a structured search filter (expression.Finalize) before dispatching it to the storage backend. Because decoding a filter validates nothing, validation and normalization happen here on behalf of every API layer; if the filter expression is structurally or semantically invalid, the error is wrapped in tracestore.ErrFilterInvalid and returned to the caller.","triggerScenarios":"A client sends a search request with query.Filter set (structured filters feature gate enabled) whose expression fails Finalize — e.g. malformed tag/field references, invalid operators, or a value that does not match the expected type of the attribute.","commonSituations":"API clients hand-crafting filter JSON instead of using a typed client; a Jaeger UI or downstream tool emitting filters against attribute names/types that changed between backend versions; proxies forwarding filters written for a different Jaeger version.","solutions":["Inspect the underlying err (via errors.Unwrap / %w chain) for the exact position or token that failed Finalize","Fix the filter expression: correct the field name, operator, and value type in the client request","Validate the filter client-side before sending, mirroring expression.Finalize rules","Confirm the structured-filters feature gate is enabled and the filter is meant to stand alone (EnsureFilterStandsAlone)"],"exampleFix":"// before\ncurl ... -d '{\"filter\":{\"field\":\"dur\",\"op\":\"~\",\"value\":\"fast\"}}'\n// after\ncurl ... -d '{\"filter\":{\"field\":\"duration\",\"op\":\">=\",\"value\":\"100ms\"}}'","handlingStrategy":"validation","validationCode":"// Client-side: validate the filter before sending\nfunc validFilter(f map[string]any) bool {\n\tfield, _ := f[\"field\"].(string)\n\top, _ := f[\"op\"].(string)\n\t_, hasVal := f[\"value\"]\n\treturn field != \"\" && op != \"\" && hasVal\n}\nif !validFilter(req.Filter) {\n\treturn errors.New(\"filter requires field, op, and value\")\n}","typeGuard":null,"tryCatchPattern":"// Go: check the sentinel error\nif err := qs.FindTraceIDs(ctx, query); err != nil {\n\tif errors.Is(err, tracestore.ErrFilterInvalid) {\n\t\treturn http.StatusBadRequest // filter, not server, is at fault\n\t}\n\treturn err\n}","preventionTips":["Mirror expression.Finalize validation rules in client code before sending filters","Use typed clients/SDK builders for structured filters instead of raw JSON","Pin API client and jaeger-query versions so filter semantics match","Test filters against a local all-in-one instance before production"],"tags":["go","query","filter-validation","api"],"backgroundTag":"invalid-filter-expression","analyzedSha":"806f4447841ecdb60519f408b004a599d515f437","analyzedAt":"2026-09-01T02:39:22.140Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}