{"record":{"id":"275ae6eb1ffe3938","repo":"usememos/memos","slug":"startswith-argument-must-be-a-string","errorCode":null,"errorMessage":"startsWith argument must be a string","messagePattern":"startsWith argument must be a string","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/filter/parser.go","lineNumber":970,"sourceCode":"// 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 {\n\t\treturn nil, errors.New(\"startsWith expects exactly one argument\")\n\t}\n\n\tprefix, err := getConstValue(call.Args[0])\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"startsWith argument must be a constant string\")\n\t}\n\n\tprefixStr, ok := prefix.(string)\n\tif !ok {\n\t\treturn nil, errors.New(\"startsWith argument must be a string\")\n\t}\n\n\treturn &StartsWithPredicate{Prefix: prefixStr}, nil\n}\n\n// buildEndsWithPredicate extracts the pattern from t.endsWith(\"suffix\").\nfunc buildEndsWithPredicate(call *exprv1.Expr_Call, iterVar string) (PredicateExpr, error) {\n\tif target := call.Target.GetIdentExpr(); target == nil || target.GetName() != iterVar {\n\t\treturn nil, errors.Errorf(\"endsWith target must be the iteration variable %q\", iterVar)\n\t}\n\n\tif len(call.Args) != 1 {\n\t\treturn nil, errors.New(\"endsWith expects exactly one argument\")\n\t}\n\n\tsuffix, err := getConstValue(call.Args[0])\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"endsWith argument must be a constant string\")","sourceCodeStart":952,"sourceCodeEnd":988,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/internal/filter/parser.go#L952-L988","documentation":"The argument to startsWith() inside a comprehension predicate must be a compile-time string constant. If the argument resolves to a non-string constant (int, bool, etc.), this error is thrown; a non-constant argument produces the wrapped 'startsWith argument must be a constant string' instead.","triggerScenarios":"tag.exists(t, t.startsWith(5)) — passing a numeric literal instead of a quoted string; similarly boolean or duration literals.","commonSituations":"Users write unquoted prefixes or assume startsWith coerces types.","solutions":["Quote the prefix: tag.exists(t, t.startsWith(\"work\"))","Remember the prefix is a SQL LIKE 'prefix%' pattern, so it must be a literal string"],"exampleFix":"// before\n`tag.exists(t, t.startsWith(5))`\n\n// after\n`tag.exists(t, t.startsWith(\"work\"))`","handlingStrategy":"validation","validationCode":"var badStartsWith = regexp.MustCompile(`startsWith\\s*\\(\\s*[^\"']`)\n\nfunc validateStartsWith(expr string) error {\n    if badStartsWith.MatchString(expr) {\n        return errors.New(\"startsWith needs a quoted string prefix\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := r.Render(cond); err != nil {\n    if strings.Contains(err.Error(), \"startsWith argument must be a string\") {\n        return userErr(\"quote the startsWith prefix, e.g. t.startsWith(\\\"work\\\")\")\n    }\n    return err\n}","preventionTips":["Quote prefix literals","Remember prefixes compile to SQL LIKE 'prefix%' patterns and must be literal strings"],"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"}