jaegertracing/jaeger · error

filter argument is empty: a call argument must carry a call

Error message

filter argument is empty: a call argument must carry a call

What it means

In toFilterExpression, an Expression_Call node whose Call field carries an argument term that decodes to nil produces this error: a filter call argument must actually contain a call, but the wire message had none. It guards the filter expression decoder against partially populated protobuf trees produced by an older or buggy encoder.

Source

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

		}, nil
	case *Expression_Nested:
		return &expression.NestedRef{
			Level: expression.Level(term.Nested.GetLevel()),
		}, 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

View on GitHub (pinned to 806f444784)

Solutions

  1. Re-encode the filter expression with a current version of the expression proto/encoder so every call argument sets exactly one of attr/field/nested/scalar/list/call.
  2. Check the sender for code paths that build an Expression_Call argument without assigning its oneof field.
  3. Validate the payload client-side before sending; treat nil-decoding terms as send errors via ErrTermNotEncodable on the encode side.

Example fix

// before (sender leaves oneof unset)
callArg := &exprv1.Expression{}
// after (set a concrete variant)
callArg := &exprv1.Expression{Value: &exprv1.Expression_Scalar{Scalar: exprv1.NewIntValue(1)}}
Defensive patterns

Strategy: try-catch

Validate before calling

for _, arg := range callTerm.Call.Args {
    if arg == nil || arg.Value == nil {
        return errors.New("call argument has no oneof set")
    }
}

Type guard

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

Try / catch

node, err := decodeCall(callTerm.Call, depth+1)
if err != nil {
    return nil, fmt.Errorf("cannot decode filter call: %w", err)
}

Prevention

When it happens

Trigger: decodeCall processes an Expression_Call whose argument list contains a term that, after nested decoding, yields nil — e.g. a call argument protobuf with no oneof variant set (all of attr, field, nested, scalar, list, call empty).

Common situations: Reading filter expressions serialized by a client that predates a proto change; manually constructed protobuf messages with unset oneof fields; corrupted or truncated filter payloads sent over the wire.

Related errors


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