usememos/memos · error

filter must evaluate to a boolean value

Error message

filter must evaluate to a boolean value

What it means

Returned by buildCondition (internal/filter/parser.go) when the AST root (or nested position reached via buildCondition) is a constant expression that is not a boolean — e.g., the whole filter is a string or number literal. The compiled program must be a boolean condition tree; non-boolean constants cannot serve as one.

Source

Thrown at internal/filter/parser.go:30

// single filter observes a single instant.
type parseContext struct {
	schema Schema
	now    time.Time
}

func buildCondition(expr *exprv1.Expr, pc parseContext) (Condition, error) {
	switch v := expr.ExprKind.(type) {
	case *exprv1.Expr_CallExpr:
		return buildCallCondition(v.CallExpr, pc)
	case *exprv1.Expr_ConstExpr:
		val, err := getConstValue(expr)
		if err != nil {
			return nil, err
		}
		if v, ok := val.(bool); ok {
			return &ConstantCondition{Value: v}, nil
		}
		return nil, errors.New("filter must evaluate to a boolean value")
	case *exprv1.Expr_IdentExpr:
		name := v.IdentExpr.GetName()
		field, ok := pc.schema.Field(name)
		if !ok {
			return nil, errors.Errorf("unknown identifier %q", name)
		}
		if field.Type != FieldTypeBool {
			return nil, errors.Errorf("identifier %q is not boolean", name)
		}
		return &FieldPredicateCondition{Field: name}, nil
	case *exprv1.Expr_ComprehensionExpr:
		return buildComprehensionCondition(v.ComprehensionExpr, pc.schema)
	default:
		return nil, errors.New("unsupported top-level expression")
	}
}

func buildCallCondition(call *exprv1.Expr_Call, pc parseContext) (Condition, error) {

View on GitHub (pinned to 14d757ce1f)

Solutions

  1. Make the filter a full boolean expression: compare a field to the value (content.contains(\"hello\")", "pinned == true").
  2. If generating filters in code, template 'field op value', never inject the raw value alone.
  3. Validate with a small CEL type-check pass or example filters before saving a user-defined filter.

Example fix

# before
"hello"

# after
content.contains("hello")
Defensive patterns

Strategy: validation

Validate before calling

// Go — smoke-compile saved filters before persisting them
if _, err := engine.Compile(ctx, candidate); err != nil {
  return errors.Wrap(err, "invalid filter")
}

Try / catch

if err != nil {
  if strings.Contains(err.Error(), "must evaluate to a boolean") {
    return status.Errorf(codes.InvalidArgument, "filter must be a boolean expression, e.g. pinned == true")
  }
  return err
}

Prevention

When it happens

Trigger: A filter like "\"hello\"" or "42" or "1.5" — CEL compiles these fine (they are valid expressions of non-bool type), but buildCondition's ConstExpr branch only accepts bool constants and returns ConstantCondition; anything else hits this error. Note CEL normally type-checks top-level bool, so this often surfaces via sub-expressions in untyped code paths.

Common situations: Users pasting a bare value instead of a comparison; quoting the entire expression; building filters programmatically and interpolating a value where a predicate was expected.

Related errors


AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15). Data as JSON: /api/errors/3bee948ddff25d61. Report an issue: GitHub.