{"record":{"id":"3bee948ddff25d61","repo":"usememos/memos","slug":"filter-must-evaluate-to-a-boolean-value","errorCode":null,"errorMessage":"filter must evaluate to a boolean value","messagePattern":"filter must evaluate to a boolean value","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/filter/parser.go","lineNumber":30,"sourceCode":"// single filter observes a single instant.\ntype parseContext struct {\n\tschema Schema\n\tnow    time.Time\n}\n\nfunc buildCondition(expr *exprv1.Expr, pc parseContext) (Condition, error) {\n\tswitch v := expr.ExprKind.(type) {\n\tcase *exprv1.Expr_CallExpr:\n\t\treturn buildCallCondition(v.CallExpr, pc)\n\tcase *exprv1.Expr_ConstExpr:\n\t\tval, err := getConstValue(expr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif v, ok := val.(bool); ok {\n\t\t\treturn &ConstantCondition{Value: v}, nil\n\t\t}\n\t\treturn nil, errors.New(\"filter must evaluate to a boolean value\")\n\tcase *exprv1.Expr_IdentExpr:\n\t\tname := v.IdentExpr.GetName()\n\t\tfield, ok := pc.schema.Field(name)\n\t\tif !ok {\n\t\t\treturn nil, errors.Errorf(\"unknown identifier %q\", name)\n\t\t}\n\t\tif field.Type != FieldTypeBool {\n\t\t\treturn nil, errors.Errorf(\"identifier %q is not boolean\", name)\n\t\t}\n\t\treturn &FieldPredicateCondition{Field: name}, nil\n\tcase *exprv1.Expr_ComprehensionExpr:\n\t\treturn buildComprehensionCondition(v.ComprehensionExpr, pc.schema)\n\tdefault:\n\t\treturn nil, errors.New(\"unsupported top-level expression\")\n\t}\n}\n\nfunc buildCallCondition(call *exprv1.Expr_Call, pc parseContext) (Condition, error) {","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/internal/filter/parser.go#L12-L48","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Make the filter a full boolean expression: compare a field to the value (content.contains(\\\"hello\\\")\", \"pinned == true\").","If generating filters in code, template 'field op value', never inject the raw value alone.","Validate with a small CEL type-check pass or example filters before saving a user-defined filter."],"exampleFix":"# before\n\"hello\"\n\n# after\ncontent.contains(\"hello\")","handlingStrategy":"validation","validationCode":"// Go — smoke-compile saved filters before persisting them\nif _, err := engine.Compile(ctx, candidate); err != nil {\n  return errors.Wrap(err, \"invalid filter\")\n}","typeGuard":null,"tryCatchPattern":"if err != nil {\n  if strings.Contains(err.Error(), \"must evaluate to a boolean\") {\n    return status.Errorf(codes.InvalidArgument, \"filter must be a boolean expression, e.g. pinned == true\")\n  }\n  return err\n}","preventionTips":["Document example boolean filters next to the input field.","Validate filters with Compile at save time, not at query time.","When templating filters, always emit 'field <op> value', never a bare value."],"tags":["cel","filter","parser","validation","go"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}