jaegertracing/jaeger · error

filter term cannot be encoded for the wire

Error message

filter term cannot be encoded for the wire

What it means

ErrTermNotEncodable is a sentinel error wrapped with the offending Go type (%T) when fromFilterExpression meets a filter term it has no wire representation for: either a nil term or a Go type the package does not recognize. It signals the filter tree was not finalized into the shape ToProto expects.

Source

Thrown at internal/proto/expression/v1/convert.go:207

	case *expression.Call:
		if term == nil {
			break
		}
		call, err := ToProto(term)
		if err != nil {
			return nil, err
		}
		return &Expression{Term: &Expression_Call{Call: call}}, nil
	}
	if scalar := fromFilterConstant(expr); scalar != nil {
		return &Expression{Term: &Expression_Scalar{Scalar: scalar}}, nil
	}
	return nil, fmt.Errorf("%w: %T", ErrTermNotEncodable, expr)
}

// ErrTermNotEncodable is returned for a term ToProto has no wire form for: a nil one, or a type
// this package does not know. Both mean the tree was not the finalized filter ToProto expects.
var ErrTermNotEncodable = errors.New("filter term cannot be encoded for the wire")

// fromFilterConstant writes a constant node as the wire's spelling plus the hint that fits it. A
// duration and an instant have no hint of their own, so they travel as an unhinted constant in
// the syntax the field they are compared against is written in — Go duration syntax and RFC 3339
// — which is the spelling the receiving side reads them back from.
// It returns nil for a term that is not a constant, and for a constant that holds nothing: a nil
// pointer of a constant type reads through the Expression interface as a constant of that type, and
// reading its value would panic. The caller answers a nil with ErrTermNotEncodable, which is what a
// tree carrying one deserves.
func fromFilterConstant(expr expression.Expression) *Scalar {
	switch term := expr.(type) {
	case *expression.AnyValue:
		if term == nil {
			return nil
		}
		return &Scalar{Value: term.Value}
	case *expression.StringValue:
		if term == nil {

View on GitHub (pinned to 806f444784)

Solutions

  1. Inspect the wrapped %T in the error and replace that term type with one of the supported finalized filter term types.
  2. Run the filter tree through the package's finalization/normalization step before calling ToProto.
  3. In callers, use errors.Is(err, exprv1.ErrTermNotEncodable) to detect this case and return a 400-style validation error to the user.
  4. Check for nil terms in the tree before encoding.

Example fix

// before
if err != nil { return err }
// after
if err != nil {
    if errors.Is(err, exprv1.ErrTermNotEncodable) {
        return httperr.BadRequest("unsupported filter term")
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

func encodable(t FilterTerm) bool { return t != nil && isSupportedTermType(t) }

Type guard

func isSupportedTerm(t any) bool {
    switch t.(type) {
    case nil:
        return false
    default:
        return isFinalizedTerm(t)
    }
}

Try / catch

pb, err := ToProto(tree)
if errors.Is(err, ErrTermNotEncodable) {
    return fmt.Errorf("filter term %w; finalize the tree first", err)
}

Prevention

When it happens

Trigger: Calling ToProto on a filter expression containing a nil term or a term type outside the package's known set (e.g. an unfinalized/internal node type); tests TestToProto_RefusesATermItCannotWrite and TestQueryParametersRefuseAnUnsendableFilter exercise this.

Common situations: Passing a query filter tree built by hand or by an older API version directly to ToProto without running the finalization step; constructing query parameters with unsupported comparison nodes.

Related errors


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