jaegertracing/jaeger · error · tracestore.ErrFilterInvalid

%w: %w

Error message

%w: %w

What it means

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.

Source

Thrown at cmd/jaeger/internal/extension/jaegerquery/querysvc/service.go:218

func (qs QueryService) prepareSearchQuery(
	ctx context.Context,
	query TraceQueryParams,
) (context.Context, TraceQueryParams, error) {
	if query.Filter != nil {
		// None of these refusals depends on the backend, so they come before the capability call
		// rather than after it.
		if !StructuredFiltersGate.IsEnabled() {
			return ctx, query, fmt.Errorf("%w: enable the %q feature gate to use it",
				ErrFilterDisabled, StructuredFiltersGate.ID())
		}
		if err := query.EnsureFilterStandsAlone(); err != nil {
			return ctx, query, err
		}
		// Decoding a filter validates nothing, so it is finalized — validated and normalized —
		// here, on behalf of every API layer above (RFC 0005 §7).
		finalized, err := expression.Finalize(query.Filter)
		if err != nil {
			return ctx, query, fmt.Errorf("%w: %w", tracestore.ErrFilterInvalid, err)
		}
		query.Filter = finalized
	}
	if len(qs.options.Interceptors) > 0 {
		var err error
		ctx, query, err = qs.onQuery(ctx, query)
		if err != nil {
			return ctx, query, err
		}
	}
	if query.Filter == nil {
		return ctx, query, qs.checkServiceName(ctx, query)
	}
	caps, err := qs.traceReader.SearchCapabilities(ctx)
	if err != nil {
		// A reader that cannot report its capabilities reads as the least capable one, which
		// serves only the legacy predicate fields.
		caps = tracestore.SearchCapabilities{}

View on GitHub (pinned to 806f444784)

Solutions

  1. Inspect the underlying err (via errors.Unwrap / %w chain) for the exact position or token that failed Finalize
  2. Fix the filter expression: correct the field name, operator, and value type in the client request
  3. Validate the filter client-side before sending, mirroring expression.Finalize rules
  4. Confirm the structured-filters feature gate is enabled and the filter is meant to stand alone (EnsureFilterStandsAlone)

Example fix

// before
curl ... -d '{"filter":{"field":"dur","op":"~","value":"fast"}}'
// after
curl ... -d '{"filter":{"field":"duration","op":">=","value":"100ms"}}'
Defensive patterns

Strategy: validation

Validate before calling

// Client-side: validate the filter before sending
func validFilter(f map[string]any) bool {
	field, _ := f["field"].(string)
	op, _ := f["op"].(string)
	_, hasVal := f["value"]
	return field != "" && op != "" && hasVal
}
if !validFilter(req.Filter) {
	return errors.New("filter requires field, op, and value")
}

Try / catch

// Go: check the sentinel error
if err := qs.FindTraceIDs(ctx, query); err != nil {
	if errors.Is(err, tracestore.ErrFilterInvalid) {
		return http.StatusBadRequest // filter, not server, is at fault
	}
	return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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