{"record":{"id":"4834edeaa5624d3e","repo":"usememos/memos","slug":"equality-argument-must-be-a-string","errorCode":null,"errorMessage":"equality argument must be a string","messagePattern":"equality argument must be a string","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/filter/parser.go","lineNumber":941,"sourceCode":"\n\tvar constExpr *exprv1.Expr\n\tswitch {\n\tcase isIterVarExpr(call.Args[0], iterVar):\n\t\tconstExpr = call.Args[1]\n\tcase isIterVarExpr(call.Args[1], iterVar):\n\t\tconstExpr = call.Args[0]\n\tdefault:\n\t\treturn nil, errors.Errorf(\"equality predicate must compare against the iteration variable %q\", iterVar)\n\t}\n\n\tvalue, err := getConstValue(constExpr)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"equality argument must be a constant string\")\n\t}\n\n\tvalueStr, ok := value.(string)\n\tif !ok {\n\t\treturn nil, errors.New(\"equality argument must be a string\")\n\t}\n\n\treturn &EqualsPredicate{Value: valueStr}, nil\n}\n\nfunc isIterVarExpr(expr *exprv1.Expr, iterVar string) bool {\n\ttarget := expr.GetIdentExpr()\n\treturn target != nil && target.GetName() == iterVar\n}\n\n// buildStartsWithPredicate extracts the pattern from t.startsWith(\"prefix\").\nfunc buildStartsWithPredicate(call *exprv1.Expr_Call, iterVar string) (PredicateExpr, error) {\n\t// Verify the target is the iteration variable\n\tif target := call.Target.GetIdentExpr(); target == nil || target.GetName() != iterVar {\n\t\treturn nil, errors.Errorf(\"startsWith target must be the iteration variable %q\", iterVar)\n\t}\n\n\tif len(call.Args) != 1 {","sourceCodeStart":923,"sourceCodeEnd":959,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/internal/filter/parser.go#L923-L959","documentation":"The == predicate inside a comprehension must compare the iteration variable to a compile-time string constant. After locating the constant side, getConstValue extracts its value; if the value exists but is not a string (e.g. int, bool, duration, timestamp), this error is returned. (A non-constant operand instead yields the wrapped 'equality argument must be a constant string'.)","triggerScenarios":"tag.exists(t, t == 42) or tag.exists(t, t == true) — comparing the iterated string (tag) to a numeric or boolean literal.","commonSituations":"Users assume tags can be numbers; copy-pasting filters from other systems where tag lists are typed differently.","solutions":["Quote the comparison value: tag.exists(t, t == \"42\")","Verify the field being iterated is a string-list field (like tag) and use string literals"],"exampleFix":"// before\n`tag.exists(t, t == 42)`\n\n// after\n`tag.exists(t, t == \"42\")`","handlingStrategy":"validation","validationCode":"// Ensure == comparisons inside macros use string literals\nvar nonStringEq = regexp.MustCompile(`==\\s*([^\"'\\s][^,)]*)`)\n\nfunc validateEqStrings(expr string) error {\n    if nonStringEq.MatchString(expr) {\n        return errors.New(\"compare tags with double-quoted string literals\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := r.Render(cond); err != nil {\n    if strings.Contains(err.Error(), \"equality argument must be a string\") {\n        return userErr(\"tag comparisons must use quoted string values\")\n    }\n    return err\n}","preventionTips":["Always quote values compared against list-of-string fields","Treat tag names as strings end-to-end, including numeric-looking tags"],"tags":["cel","filter","parser","comprehension","type-mismatch"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}