usememos/memos · error
contains argument must be a string
Error message
contains argument must be a string
What it means
The argument to contains() in a comprehension predicate must be a compile-time string constant. A constant of another type triggers this error; non-constant arguments surface as the wrapped 'contains argument must be a constant string'.
Source
Thrown at internal/filter/parser.go:1016
// buildContainsPredicate extracts the pattern from t.contains("substring").
func buildContainsPredicate(call *exprv1.Expr_Call, iterVar string) (PredicateExpr, error) {
if target := call.Target.GetIdentExpr(); target == nil || target.GetName() != iterVar {
return nil, errors.Errorf("contains target must be the iteration variable %q", iterVar)
}
if len(call.Args) != 1 {
return nil, errors.New("contains expects exactly one argument")
}
substring, err := getConstValue(call.Args[0])
if err != nil {
return nil, errors.Wrap(err, "contains argument must be a constant string")
}
substringStr, ok := substring.(string)
if !ok {
return nil, errors.New("contains argument must be a string")
}
return &ContainsPredicate{Substring: substringStr}, nil
}
View on GitHub (pinned to 14d757ce1f)
Solutions
- Quote the substring: tag.exists(t, t.contains("work"))
- Use string literals only; bind dynamic values before the filter or use == with a constant
Example fix
// before
`tag.exists(t, t.contains(42))`
// after
`tag.exists(t, t.contains("work"))` Defensive patterns
Strategy: validation
Validate before calling
var badContains = regexp.MustCompile(`contains\s*\(\s*[^"']`)
func validateContains(expr string) error {
if badContains.MatchString(expr) {
return errors.New("contains needs a quoted string substring")
}
return nil
} Try / catch
if err := r.Render(cond); err != nil {
if strings.Contains(err.Error(), "contains argument must be a string") {
return userErr("quote the contains substring, e.g. t.contains(\"work\")")
}
return err
} Prevention
- Quote substring literals
- Never pass numbers or booleans to contains on string-list fields
When it happens
Trigger: tag.exists(t, t.contains(42)) or tag.all(t, t.contains(dyn_value)) with a non-string literal — comparing the iterated tag against a numeric/boolean literal.
Common situations: Unquoted substrings; users assuming contains accepts any scalar type.
Related errors
- equality argument must be a string
- startsWith argument must be a string
- endsWith argument must be a string
- text match argument must be a string
- comprehension accumulator must be initialized with a constan
AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15).
Data as JSON: /api/errors/0df8b3fc8b5a069f.
Report an issue: GitHub.