jaegertracing/jaeger · error

filter argument is empty: exactly one of attr, field, nested

Error message

filter argument is empty: exactly one of attr, field, nested, scalar, list or call must be set

What it means

The default branch of toFilterExpression fires when an Expression's oneof holds none of the recognized variants (attr, field, nested, scalar, list, call). The wire message is structurally an Expression but carries no term, so no filter node can be built. It is the catch-all against empty or unknown oneof cases.

Source

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

		}, nil
	case *Expression_Scalar:
		return toFilterConstant(term.Scalar)
	case *Expression_List:
		return &expression.List{
			Values: term.List.GetValues(),
			Type:   expression.ValueType(term.List.GetType()),
		}, nil
	case *Expression_Call:
		nested, err := decodeCall(term.Call, depth+1)
		if err != nil {
			return nil, err
		}
		if nested == nil {
			return nil, errors.New("filter argument is empty: a call argument must carry a call")
		}
		return nested, nil
	default:
		return nil, errors.New("filter argument is empty: exactly one of attr, field, nested, scalar, list or call must be set")
	}
}

// toFilterConstant reads a wire constant as the node that holds it. The type hint is optional and
// authoritative when set (RFC 0005 §5.4), so an unhinted constant becomes the untyped node — a
// duration or an instant among them, since the wire has no hint for either and what a spelling
// like "2s" means is settled by the field it is compared against (expression.ResolveConstants).
func toFilterConstant(scalar *Scalar) (expression.Expression, error) {
	value := scalar.GetValue()
	switch valueType := expression.ValueType(scalar.GetType()); valueType {
	case "":
		return &expression.AnyValue{Value: value}, nil
	case expression.ValueTypeString:
		return &expression.StringValue{Value: value}, nil
	case expression.ValueTypeInt:
		number, err := strconv.ParseInt(value, 10, 64)
		if err != nil {
			return nil, errNotOfDeclaredType(value, valueType)

View on GitHub (pinned to 806f444784)

Solutions

  1. Ensure the encoder sets exactly one oneof variant on every Expression before serialization.
  2. Align proto definitions and jaeger versions between sender and receiver so all variants are known.
  3. Reject unencodable terms at encode time (fromFilterExpression returns ErrTermNotEncodable) instead of letting empty terms reach the wire.

Example fix

// before
expr := &exprv1.Expression{}
// after
expr := &exprv1.Expression{Value: &exprv1.Expression_Attr{Attr: &exprv1.Attribute{Key: "service.name"}}}
Defensive patterns

Strategy: validation

Validate before calling

func validExpr(e *exprv1.Expression) bool {
    switch v := e.GetValue().(type) {
    case *exprv1.Expression_Attr, *exprv1.Expression_Field, *exprv1.Expression_Nested,
        *exprv1.Expression_Scalar, *exprv1.Expression_List, *exprv1.Expression_Call:
        _ = v
        return true
    default:
        return false
    }
}

Type guard

func isKnownTerm(e *exprv1.Expression) bool {
    return e != nil && e.Value != nil
}

Try / catch

node, err := toFilterExpression(term, depth)
if err != nil {
    return nil, fmt.Errorf("filter expression invalid: %w", err)
}

Prevention

When it happens

Trigger: decodeCall encounters a term whose oneof value is unset (nil) or set to a variant this package version does not know; also any non-Call term falling through to default with no variant populated.

Common situations: Producer compiled against a newer expression proto with a variant the consumer doesn't understand; empty Expression structs serialized by mistake; hand-crafted test fixtures missing the oneof assignment.

Related errors


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